PHPのお勉強!

PHP TOP

クラスの基礎

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 アトリビュートを指定しても動的なプロパティを作成できなくなります。 動的なプロパティを作成しようとすると、コンパイル時にエラーが発生します。

<?php
#[\AllowDynamicProperties]
readonly class
Foo {
}

// Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
?>

型を指定していないプロパティや、 static プロパティ に対しては readonly を指定できません。 readonly クラスには、それらをいずれも指定できません:

<?php
readonly class Foo
{
public
$bar;
}

// Fatal error: Readonly property Foo::$bar must have type
?>
<?php
readonly class Foo
{
public static
int $bar;
}

// Fatal error: Readonly class Foo cannot declare static properties
?>

readonly を指定したクラスは、 子クラスでも readonly を指定した場合にのみ 継承 できます。

new

あるクラスのインスタンスを生成するには、new キーワードを使わなければなりません。エラー時に 例外をスローするような コンストラクタを定義していない限り、 オブジェクトが常に生成されます。 クラスは、そのインスタンスを作成する前に定義すべきです (これが必須となる場合もあります)。

クラス名を含む文字列を new で指定すると、 そのクラスのインスタンスを作成します。クラスが名前空間に属している場合は、 完全修飾名を指定しなければなりません。

注意:

クラスのコンストラクタに引数を渡さなかった場合、 クラス名の後の括弧は省略しても構いません。

例3 インスタンスを作成する

<?php
$instance
= new SimpleClass();

// 変数を使うこともできます
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>

クラスのコンテキストにおいては、 new selfnew 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');
?>

上の例の出力は、 たとえば以下のようになります。

2016

注意: 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;

上の例の出力は以下となります。

property
method

これはつまり、プロパティに 無名関数 を代入した場合に、その関数は直接呼び出せないということです。 その場合は、たとえば事前にプロパティを変数に代入しておく必要があります。 括弧で囲むと、プロパティを直接呼び出すこともできます。

例9 プロパティに格納した無名関数の呼び出し

<?php
class Foo
{
public
$bar;

public function
__construct() {
$this->bar = function() {
return
42;
};
}
}

$obj = new Foo();

echo (
$obj->bar)(), PHP_EOL;

上の例の出力は以下となります。

42

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);

上の例の出力は以下となります。

Valid
Valid

次の例は、引数を削除した子クラスのメソッドや、 オプションの引数を必須にしたりすることが、親クラスのメソッドと互換性がなくなることを示しています。

例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