クラスの基礎
class
各クラスの定義は、class
キーワードで始まり、クラス名が続きます。
そしてその後に波括弧のペアが続き、
その中にはクラスのプロパティとメソッドの定義を記述します。
クラス名には、PHP の予約語
以外でラベルとして有効なあらゆる名前を使用することができます。
PHP 8.4.0 以降では、アンダースコア (_
)
1文字のみのクラス名は非推奨となりました。
有効なクラス名は、先頭が文字あるいはアンダースコアで始まり、
その後に任意の数の文字/数字/アンダースコアが続くものです。
正規表現で表すと、
^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$
のようになります。
クラスの中には、
定数 や
変数
("プロパティ" といいます) そして関数 ("メソッド" といいます)
を含めることができます。
例1 簡単なクラス定義
<?php
class SimpleClass
{
// プロパティの宣言
public $var = 'a default value';
// メソッドの宣言
public function displayVar() {
echo $this->var;
}
}
?>
メソッドがオブジェクトのコンテキストからコールされる場合は、
疑似変数 $this が利用可能です。
$this は、呼び出し元オブジェクトの値です。
警告
static でないメソッドを static メソッドとしてコールすると、
Error がスローされます。
PHP 8.0.0 より前のバージョンでは、推奨されない警告が発生し、
$this が未定義になっていました。
例2 $this 疑似変数の例
<?php
class A
{
function foo()
{
if (isset($this)) {
echo '$this is defined (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this is not defined.\n";
}
}
}
class B
{
function bar()
{
A::foo();
}
}
$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
上の例の PHP 7 での出力は、このようになります。
$this is defined (A)
Deprecated: Non-static method A::foo() should not be called statically in %s on line 27
$this is not defined.
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
$this is not defined.
Deprecated: Non-static method B::bar() should not be called statically in %s on line 32
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
$this is not defined.
上の例の PHP 8 での出力は、このようになります。:
$this is defined (A)
Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27
Stack trace:
#0 {main}
thrown in %s on line 27
読み取り専用クラス
PHP 8.2.0 以降では、
クラスに対して readonly を指定することができます。
クラスに対して readonly を指定すると、
宣言されている全ての プロパティに対して readonly を指定した ことになり、
かつ 動的なプロパティ の作成を禁止したことになります。
さらに、AllowDynamicProperties
アトリビュートを指定しても動的なプロパティを作成できなくなります。
動的なプロパティを作成しようとすると、コンパイル時にエラーが発生します。
型を指定していないプロパティや、
static プロパティ に対しては readonly
を指定できません。
readonly クラスには、それらをいずれも指定できません:
readonly を指定したクラスは、
子クラスでも readonly を指定した場合にのみ
継承 できます。
new
あるクラスのインスタンスを生成するには、new
キーワードを使わなければなりません。エラー時に
例外をスローするような
コンストラクタを定義していない限り、
オブジェクトが常に生成されます。
クラスは、そのインスタンスを作成する前に定義すべきです
(これが必須となる場合もあります)。
クラス名を含む文字列を new
で指定すると、
そのクラスのインスタンスを作成します。クラスが名前空間に属している場合は、
完全修飾名を指定しなければなりません。
注意:
クラスのコンストラクタに引数を渡さなかった場合、
クラス名の後の括弧は省略しても構いません。
例3 インスタンスを作成する
<?php
$instance = new SimpleClass();
// 変数を使うこともできます
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
クラスのコンテキストにおいては、
new self
や new parent
のようにして新しいオブジェクトを作成することができます。
作成済みのクラスのインスタンスを新たな変数に代入する場合、新しい変数は、
代入されたオブジェクトと同じインスタンスにアクセスします。
この動作は、インスタンスを関数に渡す場合も同様です。
作成済みのオブジェクトのコピーは、その
クローンを作成
することにより作成可能です。
例4 オブジェクトの代入
<?php
$instance = new SimpleClass();
$assigned = $instance;
$reference =& $instance;
$instance->var = '$assigned will have this value';
$instance = null; // $instance と $reference は null になります
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>
NULL
NULL
object(SimpleClass)#1 (1) {
["var"]=>
string(30) "$assigned will have this value"
}
PHP 8.0.0 以降では、
new
を任意の式と一緒に使う機能がサポートされました。
これによって、式が文字列を生成する場合に、
より複雑なインスタンス化を行えるようになります。
式は括弧で囲まなければいけません。
例5 任意の式を使ってインスタンスを生成する
以下の例では、
クラス名を生成する有効な任意の式を複数示します。
関数呼び出し、文字列連結、そして
::class
定数です。
<?php
class ClassA extends \stdClass {}
class ClassB extends \stdClass {}
class ClassC extends ClassB {}
class ClassD extends ClassA {}
function getSomeClass(): string
{
return 'ClassA';
}
var_dump(new (getSomeClass()));
var_dump(new ('Class' . 'B'));
var_dump(new ('Class' . 'C'));
var_dump(new (ClassD::class));
?>
上の例の PHP 8 での出力は、このようになります。:
object(ClassA)#1 (0) {
}
object(ClassB)#1 (0) {
}
object(ClassC)#1 (0) {
}
object(ClassD)#1 (0) {
}
複数のやり方で、オブジェクトのインスタンスを作ることが出来ます:
例6 新しいオブジェクトの作成
<?php
class Test
{
static public function getNew()
{
return new static;
}
}
class Child extends Test
{}
$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);
$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);
$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);
?>
bool(true)
bool(true)
bool(true)
新しく作成したオブジェクトのメンバーに、作成したその式の中でもアクセスすることができます。
例7 新しく作成したオブジェクトのメンバーへのアクセス
<?php
echo (new DateTime())->format('Y');
// PHP 8.4.0 以降、囲む括弧は省略可能です。
echo new DateTime()->format('Y');
?>
注意:
PHP 7.1 より前のバージョンでは、コンストラクタが定義されない場合、それへの引数が評価されていませんでした。
プロパティとメソッド
クラスのプロパティとメソッドは、それぞれ別の "名前空間" に存在するので、
同じ名前のプロパティとメソッドを共存させることもできます。
プロパティを参照する場合もメソッドを参照する場合も書きかたは同じです。
それがプロパティへのアクセスなのかメソッドの呼び出しなのかは、そのコンテキストによって決まります。
つまり、変数にアクセスしようとしているのか関数を呼び出そうとしているのかの違いです。
例8 プロパティへのアクセスとメソッドの呼び出し
<?php
class Foo
{
public $bar = 'property';
public function bar() {
return 'method';
}
}
$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;
これはつまり、プロパティに 無名関数
を代入した場合に、その関数は直接呼び出せないということです。
その場合は、たとえば事前にプロパティを変数に代入しておく必要があります。
括弧で囲むと、プロパティを直接呼び出すこともできます。
例9 プロパティに格納した無名関数の呼び出し
<?php
class Foo
{
public $bar;
public function __construct() {
$this->bar = function() {
return 42;
};
}
}
$obj = new Foo();
echo ($obj->bar)(), PHP_EOL;
extends
クラスは、宣言部に extends
キーワードを含めることで、
他のクラスの定数や、メソッド、
およびプロパティを継承することができます。多重継承を行うことはできず、クラスが継承できるベース
クラスは一つだけです。
継承された定数やメソッド、プロパティをオーバーライド(上書き)するには、
親クラスで定義されているのと同じ名前でそれを再宣言します。
しかし、親クラスでそのメソッドや定数が
final
として定義されている場合はオーバーライドできません。
オーバーライドされた元のメソッドやstaticプロパティにアクセスするには、
parent::
で参照します。
注意:
PHP 8.1.0 以降では、定数も final として宣言できます。
例10 簡単なクラスの継承
<?php
class ExtendClass extends SimpleClass
{
// 親クラスのメソッドを再定義
function displayVar()
{
echo "Extending class\n";
parent::displayVar();
}
}
$extended = new ExtendClass();
$extended->displayVar();
?>
Extending class
a default value
シグネチャの互換性に関するルール
メソッドをオーバーライドするときは、
子クラスのシグネチャが親クラスのそれと互換性がなければいけません。
互換性が壊れた場合、致命的なエラーが発生します。
PHP 8.0.0 より前のバージョンでは、
互換性が壊れた場合に、E_WARNING
レベルの警告が発生していました。
共変性と反変性
の規則を守っている場合は、シグネチャに互換性があります。
必須の引数をオプションにした場合も、互換性があります。
新しいオプションの引数を追加しただけで、アクセス権を厳しくせず、
緩めただけの場合も互換性があります。
これは、リスコフの置換原則(Liskov Substitution Principle)、
略して LSP として知られています。
但し、コンストラクタ
と private
メソッドについては、
この規則の例外で、
オーバライドしたシグネチャにミスマッチがあっても致命的なエラーにはなりません。
例11 互換性がある子クラスのメソッド
<?php
class Base
{
public function foo(int $a) {
echo "Valid\n";
}
}
class Extend1 extends Base
{
function foo(int $a = 5)
{
parent::foo($a);
}
}
class Extend2 extends Base
{
function foo(int $a, $b = 5)
{
parent::foo($a);
}
}
$extended1 = new Extend1();
$extended1->foo();
$extended2 = new Extend2();
$extended2->foo(1);
次の例は、引数を削除した子クラスのメソッドや、
オプションの引数を必須にしたりすることが、親クラスのメソッドと互換性がなくなることを示しています。
例12 子クラスのメソッドで引数を削除すると致命的なエラーになる
<?php
class Base
{
public function foo(int $a = 5) {
echo "Valid\n";
}
}
class Extend extends Base
{
function foo()
{
parent::foo(1);
}
}
上の例の PHP 8 での出力は、たとえば以下のようになります。:
Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13
例13 子クラスのメソッドで、オプションの引数を必須にすると致命的なエラーになる
<?php
class Base
{
public function foo(int $a = 5) {
echo "Valid\n";
}
}
class Extend extends Base
{
function foo(int $a)
{
parent::foo($a);
}
}
上の例の PHP 8 での出力は、たとえば以下のようになります。:
Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
警告
メソッドの引数の名前を子クラスで変更しても、
シグネチャ上は非互換になりません。
しかし、こうしてしまうと
名前付き引数
を使った時に実行時エラーになるので、おすすめできません。
例14 子クラスで引数の名前を変更し、かつ名前付き引数を使うとエラーになる
<?php
class A {
public function test($foo, $bar) {}
}
class B extends A {
public function test($a, $b) {}
}
$obj = new B;
// A::test() の規約に従って引数を渡す
$obj->test(foo: "foo", bar: "bar"); // エラー発生!
Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
thrown in /in/XaaeN on line 14
::class
class
キーワードでもクラス名の解決を行うことが出来ます。
クラスの名前が ClassName
になっているクラスの完全修飾名を取得するには、
ClassName::class
を使います。
これは、名前空間付きのクラスに使うと特に便利です。
例15 クラス名の解決
<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>
注意:
::class
によるクラス名の解決は、コンパイル時の変換です。
つまり、クラス名を作るタイミングでは、まだオートロードが行われていないということです。
結果的に、クラスがまだ存在しない時点でクラス名が展開されることになります。
この場合にはエラーは発生しません。
例16 クラス名が存在しない場合の名前解決
<?php
print Does\Not\Exist::class;
?>
PHP 8.0.0 以降では、
::class
はオブジェクトに対しても使えるようになりました。
この名前解決はコンパイル時ではなく、実行時に行われます。
この場合、get_class をオブジェクトに対して使った時と同じ動きをします。
例17 オブジェクトの名前解決
<?php
namespace NS {
class ClassName {
}
}
$c = new ClassName();
print $c::class;
?>
nullsafe メソッドとプロパティ
PHP 8.0.0 以降では、プロパティやメソッドは
"nullsafe" 演算子 ?->
を使ってアクセスすることもできます。
nullsafe 演算子は既に述べたメソッドやプロパティと同じように振る舞いますが、
オブジェクトが null
と評価された場合に、例外はスローされず、
null
が返される点だけが異なります。
オブジェクトの評価がチェインの一部だった場合は、
残りのチェインはスキップされます。
この演算子の働きは、
オブジェクトにアクセスするたびに is_null でラップするコードに似ています。
しかし、よりコンパクトです。
例18 nullsafe 演算子
<?php
// PHP 8.0.0 以降は、このように書けます:
$result = $repository?->getUser(5)?->name;
// これは、以下のコードチェックと同等です:
if (is_null($repository)) {
$result = null;
} else {
$user = $repository->getUser(5);
if (is_null($user)) {
$result = null;
} else {
$result = $user->name;
}
}
?>
注意:
nullsafe 演算子は、プロパティやメソッドが値を返す際、
null が正しく、期待されうる値とみなせる場合に一番よく使います。
エラーを示すためなら、例外をスローするほうが好ましいです。