コンテンツにスキップ

PHP/クラス

出典: フリー教科書『ウィキブックス(Wikibooks)』
< PHP

本章では、PHPにおけるオブジェクト指向プログラミングの基礎について説明します。オブジェクト指向は、現代のプログラミング言語で広く採用されているパラダイムであり、PHPでも重要な概念の一つです。

PHPのオブジェクト指向は、バージョン5から導入され、現在ではPHP 8.xまで進化を遂げています。オブジェクト指向プログラミングには、クラス、オブジェクト、継承、ポリモーフィズムなどの概念があり、これらを理解することでより柔軟かつ効率的なプログラムを作成することができます。

本章では、オブジェクト指向の基本的な概念から始め、クラスの定義、オブジェクトの生成、メソッドの呼び出し、継承などを順に説明していきます。また、実際の例を交えて、具体的なプログラムの作成方法を解説します。

オブジェクト指向は、初心者にとっては難しいと感じるかもしれませんが、一度理解してしまえば、より高度なプログラミングを行うことができます。本章を通じて、オブジェクト指向プログラミングについてしっかりと理解し、PHPのプログラミング能力を向上させてください。

PHPのオブジェクト指向

[編集]

PHPのオブジェクト指向の特徴

[編集]

PHPのオブジェクト指向の主な特徴は以下の通りです。

  1. クラスとオブジェクトのサポート:PHPはクラスとオブジェクトをサポートしており、オブジェクト指向プログラミングが可能です。クラスを定義することで、オブジェクトを生成し、オブジェクトに対してメソッドを呼び出したりプロパティを設定したりできます。
  2. カプセル化:PHPでは、クラスのプロパティやメソッドをカプセル化することができます。つまり、クラスの内部で定義したメソッドやプロパティは、外部から直接アクセスできなくなります。代わりに、公開されたメソッドを介してアクセスする必要があります。これにより、コードの再利用性や保守性が向上します。
  3. 継承:PHPでは、継承をサポートしています。つまり、既存のクラスを拡張して、新しいクラスを作成することができます。継承を使うことで、重複するコードを避け、プログラムの保守性を向上させることができます。なお、多重継承はできません。
  4. 多態性:PHPでは、多態性をサポートしています。つまり、同じメソッド名で異なるクラスのオブジェクトに対して、異なる処理を行うことができます。これにより、コードの再利用性や柔軟性が向上します。
  5. 抽象クラスとインターフェース:PHPでは、抽象クラスやインターフェースをサポートしています。抽象クラスは、具象クラスの共通の機能をまとめるために使用されます。インターフェースは、実装すべきメソッドを定義するために使用され、異なるクラスで同じインターフェースを実装することができます。
  6. 名前空間:PHP 5.3 以降では、名前空間という機能が導入され、クラス名や関数名の重複を回避することができるようになりました。これにより、大規模なプログラムでも、クラスや関数の名前が重複することを回避できます。

PHPのオブジェクト指向の長所短所

[編集]

PHPのオブジェクト指向プログラミングの長所と短所については以下の通りです。

【長所】

  • 再利用性が高くなる: クラスのインスタンスを生成し、メソッドを呼び出すことで処理を行うため、同じような処理を複数回実装する必要がなくなり、コードの再利用性が高くなります。
  • 保守性が高くなる: クラスという単位でコードがまとまるため、処理の追加・変更が容易になります。また、変更時にクラス内の変更が終われば、他のコードに影響を与えないようになります。
  • 拡張性が高くなる: クラスに新たなメソッドやプロパティを追加することで、機能の追加が容易になります。また、新しいクラスを継承して新たなクラスを作成することで、既存のクラスを拡張することができます。

【短所】

  • 性能に影響が出る: オブジェクト指向プログラミングは、クラスのインスタンス生成やメソッドの呼び出しなど、多くの処理が発生するため、その分性能に影響が出ることがあります。

学習コストが高くなる: オブジェクト指向プログラミングは、手続き型プログラミングに比べて、抽象的な概念が多く登場するため、学習コストが高くなることがあります。

  • デバッグが困難になる: クラスの関係性が複雑になると、どこでバグが発生しているのかを特定するのが難しくなります。また、オブジェクト指向プログラミングの特徴である「継承」という仕組みがあるため、継承関係を把握しなければならない場合があります。

Hello World!:オブジェクト指向版

[編集]
hello.php
<?php
class Hello {
    private string $who;

    public function __construct(string $who = "World") {
        $this->who = $who;
    }
    public function greet() : void {
        echo "Hello {$this->who}!", PHP_EOL;
    }
}

$hello = new Hello;
$hello->greet();
$universe = new Hello("Universe");
$universe->greet();

?>
実行結果
Hello World!
Hello Universe!

クラス Hello は、privateアクセス修飾子を持つ$whoプロパティを持ち、__construct()メソッドで初期化されます。__construct()メソッドは、クラスがインスタンス化されたときに呼び出され、引数 $who が渡された場合は、それを$this->who に設定します。引数が渡されない場合は、デフォルトで "World" を設定します。

greet() メソッドは、インスタンスの $who プロパティを使用して、 "Hello {who}!" というメッセージを表示します。PHP_EOLは、プラットフォームに依存しない改行コードです。

次に、$helloという名前の Hello クラスのインスタンスを作成し、greet() メソッドを呼び出します。これにより、「Hello World!」が表示されます。次に、$universeという名前の別の Hello クラスのインスタンスを作成し、greet()メソッドを呼び出します。これにより、「Hello Universe!」が表示されます。

コンストラクターとアクセス修飾子とreadonlyプロパティ

[編集]

コンストラクターとプロパティは、クラスにおいて重要な役割を果たします。コンストラクターは、インスタンス化されたオブジェクトの初期化を行うためのメソッドであり、プロパティはオブジェクトのデータを表すためのメンバー変数のようなものです。

アクセス修飾子は、コンストラクターとプロパティのアクセスレベルを定義するために使用されます。アクセス修飾子は、public、private、protectedの3つがあり、それぞれ以下のような意味を持ちます。

  • public:どのクラスからでもアクセス可能
  • private:同じクラス内からのみアクセス可能
  • protected:同じクラス内およびその派生クラスからのみアクセス可能

また、プロパティの値を初期化後に変更できないようにするには、readonly修飾子を使用することができます。readonly修飾子は、一度設定された値を変更できないプロパティを定義する場合に便利です。

これらの修飾子を適切に使うことで、コードの保守性や安全性を高めることができます

hello2.php
<?php
class Hello2
{
    public function __construct(
        public readonly string $who = "World"
    ) {}
    public function greet()
    {
        echo "Hello {$this->who}!", PHP_EOL;
    }
}

$hello = new Hello2();
$hello->greet();
$universe = new Hello2("Universe");
$universe->greet();
echo '$hello->who --> ', $hello->who, PHP_EOL;
echo '$universe->who --> ', $universe->who, PHP_EOL;
// $universe->who = "Japon"; // PHP Fatal error:  Uncaught Error: Cannot modify readonly property Hello::$who in /workspace/Main.php:17

?>
実行結果
Hello World!
Hello Universe!
$hello->who --> World
$universe->who --> Universe

このコードは、PHP 8から導入された、コンストラクター引数にアクセス修飾子を組み合わせてプロパティを定義することができる機能を使用しています。また、readonly修飾子を使用して、値を一度設定した後に変更できないようにプロパティを定義しています。

具体的には、Hello2クラスでは、コンストラクターの引数として、public修飾子とreadonly修飾子が指定されたwhoという名前のstring型のプロパティを定義しています。コンストラクターでは、このプロパティに初期値が設定されます。greetメソッドは、このプロパティの値を用いて、Helloというメッセージを出力するために使用されます。

このクラスのインスタンスを生成する際には、コンストラクターに引数を渡すことができます。Hello2のインスタンスを生成する際に引数を渡さない場合、whoプロパティの値は "World" に設定されます。引数を渡した場合は、その値がwhoプロパティに設定されます。

また、コードの最後の行では、インスタンスが生成された後にプロパティの値を取得し、出力しています。その後、whoプロパティを書き換えようとしている行がコメントアウトされています。これは、readonly修飾子を使用してwhoプロパティが一度設定された後に変更できないようにしているため、エラーが発生します。

デストラクター

[編集]

デストラクターとは、オブジェクトが破棄される際に自動的に呼び出される特殊なメソッドです。デストラクターは、クラス定義内に __destruct() という名前で定義されます。デストラクターは、インスタンスを参照する変数が1つもなくなると自動的に呼び出され、そのインスタンスに関連する資源を解放するために使用されます。資源は、メモリ、ファイル、データベース接続など様々です。

また、readonly 修飾子を使用して、初期値から変更できないプロパティを作成することもできます。以下は、readonly 修飾子を使った例です。

destruct.php
<?php
class MyClass {
    function __destruct() {
        echo __CLASS__, " done!", PHP_EOL;
    }
}

echo '$obj = new MyClass()', PHP_EOL;
$obj = new MyClass();
echo '$o2 = $obj', PHP_EOL;
$o2 = $obj;
echo '$obj = null', PHP_EOL;
$obj = null;
echo '$o2 = null', PHP_EOL;
$o2 = null;
echo __FILE__, " done!", PHP_EOL;
?>
実行結果
$obj = new MyClass()
$o2 = $obj
$obj = null
$o2 = null
MyClass done!
/workspace/Main.php done!

このコードでは、クラス MyClass のインスタンスが作成され、参照が $obj で保持されます。

その後、$obj の複製が $o2 に作成され、参照がコピーされます。

次に、$objnull が代入されることで、$obj からの参照はなくなりますが、$o2 からの参照は残っています。

最後に、$o2null が代入されることで、すべての参照がなくなります。

その結果、MyClass::__destruct() が暗黙的に呼び出されます。

__destruct() は、オブジェクトが破棄される際に自動的に呼び出される特殊なメソッドであり、この例ではクラス名と "done!" という文字列が出力されます。

動的プロパティの非推奨と __get と __set

[編集]

PHPには、動的プロパティ(dynamic properties)という機能があります。これは、オブジェクトのプロパティを実行時に定義することができる機能です。しかし、動的プロパティは1つのタイポで意図しないプロパティを作ってしまい、非常に危険であり、プログラムのメンテナンス性を低下させる可能性があります。そのため、PHPでは動的プロパティの使用を非推奨としており、代わりに __get() と __set() メソッドを使用することを推奨しています。

__get() メソッドは、オブジェクトのプロパティにアクセスする際に、存在しないプロパティにアクセスした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティにアクセスされた場合に特定の処理を行うことができます。

__set() メソッドは、オブジェクトのプロパティに値を設定する際に、存在しないプロパティに値を設定しようとした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティに値を設定された場合に特定の処理を行うことができます。

これらのメソッドを使用することで、動的プロパティの使用を避け、プログラムのメンテナンス性を向上させることができます。

動的プロパティ

[編集]
動的プロパティの例
<?php
class Simple {
    public int $prop1 = 1;
}

$instance = new Simple();
echo "\$instance->prop1 --> {$instance->prop1}", PHP_EOL;
var_dump(get_object_vars($instance));
echo PHP_EOL;

$instance->prop42 = 42;
echo "\$instance->prop42 --> {$instance->prop42}", PHP_EOL;
var_dump(get_object_vars($instance));

?>
実行結果
$instance->prop1 --> 1
array(1) {
  ["prop1"]=>
  int(1)
}

$instance->prop42 --> 42
array(2) {
  ["prop1"]=>
  int(1)
  ["prop42"]=>
  int(42)
}

このコードは、クラス Simple を定義し、その中にプロパティ $prop1 を宣言しています。その後、インスタンス $instance を作成し、そのプロパティ $prop1 にアクセスして値を表示し、get_object_vars() 関数を使用して、オブジェクトのプロパティの一覧を表示しています。この結果、$instance->prop1 の値が 1 で、get_object_vars() 関数が $prop1 を返していることがわかります。

次に、$instance->prop42 というプロパティに値 42 を設定して、その値を表示し、再度 get_object_vars() 関数を使用して、オブジェクトのプロパティの一覧を表示しています。この結果、$instance->prop42 の値が 42 で、get_object_vars() 関数が $prop1 と $prop42 を返していることがわかります。

このように、PHPでは、既存のプロパティの他に、動的に新しいプロパティを追加することもできます。また、get_object_vars() 関数を使用することで、オブジェクトのプロパティを一覧できます。

__get と __set

[編集]

__setと__getは動的プロパティへの代入と参照をインターセプトする特殊関数で、いわゆるアクセサです。

__get と __set
<?php
class Simple {
    public int $prop1 = 1;
    private $ary = array();

    public function __set($name, $value) {
        echo __METHOD__, "($name, $value)" . PHP_EOL;
        $this->ary[$name] = $value;
    }
    public function __get($name) {
        $value = $this->ary[$name];
        echo __METHOD__, "($name) = $value" . PHP_EOL;
        return $value;
    }
}

$instance = new Simple();
echo "\$instance->prop1 --> {$instance->prop1}", PHP_EOL;
var_dump(get_object_vars($instance));

$instance->prop42 = 42;
echo "\$instance->prop42 --> {$instance->prop42}", PHP_EOL;
var_dump(get_object_vars($instance));

?>
実行結果
$instance->prop1 --> 1
array(1) {
  ["prop1"]=>
  int(1)
}
Simple::__set(prop42, 42)
Simple::__get(prop42) = 42
$instance->prop42 --> 42
array(1) {
  ["prop1"]=>
  int(1)
}
動的プロパティとして振舞っているのは、array型(連想配列)のプロパティ ary のキーと値のペアで、いまは echo しているだけですが、受入検査を行ったり例外を上げたりするフックとして有用です。

このコードは、クラス Simple を定義し、その中で __get() メソッドと __set() メソッドを実装しています。

__get() メソッドは、オブジェクトのプロパティにアクセスする際に、存在しないプロパティにアクセスした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティにアクセスされた場合に特定の処理を行うことができます。この例では、プロパティ名をキーとして配列 $ary から値を取得しています。

__set() メソッドは、オブジェクトのプロパティに値を設定する際に、存在しないプロパティに値を設定しようとした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティに値を設定された場合に特定の処理を行うことができます。この例では、プロパティ名をキーとして配列 $ary に値を設定しています。

クラス Simple のインスタンスを作成し、そのプロパティにアクセスすると、__get() メソッドが呼び出されます。最初にアクセスしたプロパティは $prop1 であり、存在するプロパティであるため、その値が表示されます。また、get_object_vars() 関数を使用して、オブジェクトのプロパティを取得し、配列として表示しています。

次に、存在しないプロパティである $prop42 に値を設定します。このとき、__set() メソッドが呼び出され、配列 $ary に値が設定されます。その後、再び $prop42 にアクセスすると、__get() メソッドが呼び出され、配列 $ary から値が取得され、表示されます。また、get_object_vars() 関数を使用して、オブジェクトのプロパティを取得し、配列として表示しています。このとき、配列 $ary の内容も表示されます。


object と array の相互変換

[編集]

object (の公開プロパティ)を array(連想配列)に変換したり、array(連想配列)を object に変換することができます。 これは、「オブジェクトの配列」を使いたい場合、簡素に表現することを可能にします。

arrayのarrayからobjectのarrayへの変換
<?php
const vect = [
    ["name" => "Tom", "age" => 18],
    ["name" => "Joe", "age" => 16],
    ["name" => "Sam", "age" => 10],
];
echo "配列の要素のハッシュを全て表示", PHP_EOL;
echo '  implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash)))', PHP_EOL;
foreach (vect as $hash) {
    echo implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash))), PHP_EOL;
}
echo PHP_EOL;

$objs = array_map(fn($hash): object => (object) $hash, vect);

echo "配列の要素のオブジェクトを全て表示", PHP_EOL;
echo '  "name:{$obj->name}, age:{$obj->age}"', PHP_EOL;
foreach ($objs as $obj) {
    echo "name:{$obj->name}, age:{$obj->age}", PHP_EOL;
}
echo PHP_EOL;

echo "配列の要素のオブジェクトを全てハッシュに再変換して表示", PHP_EOL;
echo '  $hash = (array)$obj;
  implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash)))', PHP_EOL;
foreach ($objs as $obj) {
    $hash = (array)$obj;
    echo implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash))), PHP_EOL;
}

?>
実行結果
配列の要素のハッシュを全て表示
  implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash)))
name:Tom, age:18
name:Joe, age:16
name:Sam, age:10

配列の要素のオブジェクトを全て表示
  "name:{$obj->name}, age:{$obj->age}"
name:Tom, age:18
name:Joe, age:16
name:Sam, age:10

配列の要素のオブジェクトを全てハッシュに再変換して表示
  $hash = (array)$obj;
  implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash)))
name:Tom, age:18
name:Joe, age:16
name:Sam, age:10

このコードは、配列とオブジェクトの相互変換の例を示しています。

配列 vect は、3つの要素を持ち、それぞれが nameage というキーを持つハッシュです。最初の foreach ループでは、vect の要素を一つずつ取り出して、array_map 関数を使って各ハッシュのキーと値を連結した文字列を作成し、それを implode 関数を使ってカンマで区切った一つの文字列にまとめて出力しています。

次に、array_map 関数と無名関数を使って、vect の各要素をオブジェクトに変換しています。このとき、array_map 関数の第二引数に渡している無名関数では、(object) キャストを使って、ハッシュをオブジェクトに変換しています。このようにすることで、各オブジェクトは name プロパティと age プロパティを持つようになります。

2つ目の foreach ループでは、配列 $objs の各要素を取り出して、name プロパティと age プロパティを連結した文字列を作成し、それを出力しています。最後に、各オブジェクトを array キャストを使って再びハッシュに変換し、キーと値を連結して文字列にして出力しています。

object はイテレーション可能

[編集]

PHPの object は array と同じくイテレーション可能です。 object は、foreach で反復可能です。

クラスの継承に当たっては、可視性のコントロールに注意が必要です。

object はイテレーション可能
<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class Base {
    public    $basePub1 = 'bPub 1';
    protected $basePro1 = 'bPro 1';
    private   $basePri1 = 'bPri 1';

    function showAll() {
       echo __METHOD__, PHP_EOL;
       foreach ($this as $key => $value) {
           echo "$key => $value", PHP_EOL;
       }
    }
}

$base = new Base();
foreach ($base as $key => $value) {
    echo "$key => $value", PHP_EOL;
}
echo PHP_EOL;
$base->showAll();

class Sub extends Base {
    public    $subPub1 = 'sPub 1';
    protected $subPro1 = 'sPro 1';
    private   $subPri1 = 'sPri 1';

    function subShowAll() {
       echo __METHOD__, PHP_EOL;
       foreach ($this as $key => $value) {
           echo "$key => $value", PHP_EOL;
       }
    }
}
echo PHP_EOL;

echo '---- class Sub ----', PHP_EOL;
$sub = new Sub();
foreach ($sub as $key => $value) {
    echo "$key => $value", PHP_EOL;
}
echo PHP_EOL;

$sub->subShowAll();
echo PHP_EOL;

$sub->showAll();
実行結果
basePub1 => bPub 1

Base::showAll
basePub1 => bPub 1
basePro1 => bPro 1
basePri1 => bPri 1

---- class Sub ----
basePub1 => bPub 1
subPub1 => sPub 1

Sub::subShowAll
basePub1 => bPub 1
basePro1 => bPro 1
subPub1 => sPub 1
subPro1 => sPro 1
subPri1 => sPri 1

Base::showAll
basePub1 => bPub 1
basePro1 => bPro 1
basePri1 => bPri 1
subPub1 => sPub 1
subPro1 => sPro 1
クラスの外からは、public なプロパティしかみえません。
クラスのメソッドからは、すべてのプロパティが見える。
派生クラスのメソッドから基底クラスのプロパティは、public に加え ptotected も見える。
派生クラスのメソッドから派生クラスのプロパティは、public だけが見える。

このコードは、クラスのプロパティにアクセスするために PHP の foreach ループを使用する方法について示しています。

また、public、protected、private プロパティへのアクセスについても説明しています。

クラス変数とクラス定数

[編集]

プロパティ(インスタンス変数)は、それぞれのインスタンスを構成する要素ですが、クラスに属する変数=クラス変数、そしてクラスに属する定数=クラス定数を定義することができます。

クラス変数とクラス定数
<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class Math
{
    public static int $var1 = 42;
    const pi = 3.14159265359;
    const e = 2.71828182846;

    public static function showVar1()
    {
        echo 'self::$var1 --> ', self::$var1, PHP_EOL;
    }
    public static function setVar1($value)
    {
        self::$var1 = $value;
    }
    public static function showPI()
    {
        echo "self::pi --> ", self::pi, PHP_EOL;
    }
}

echo "クラス変数の参照と更新", PHP_EOL;
echo 'Math::$var1 --> ', Math::$var1, PHP_EOL;
echo 'Math::$var1 = 4423;', PHP_EOL;
Math::$var1 = 4423;
echo 'Math::$var1 --> ', Math::$var1, PHP_EOL;
echo "メソッド経由のクラス変数の参照と更新", PHP_EOL;
echo "Math::showVar1():", PHP_EOL;
Math::showVar1();
echo "Math::setVar1(123);", PHP_EOL;
Math::setVar1(123);
echo "Math::showVar1():", PHP_EOL;
Math::showVar1();

echo "クラス定数の参照", PHP_EOL;
echo "Math::pi --> ", Math::pi, PHP_EOL;
echo "Math::e --> ", Math::e, PHP_EOL;
echo "メソッド経由のクラス定数の参照と更新", PHP_EOL;
echo "Math::showPI():", PHP_EOL;
Math::showPI();
実行結果
クラス変数の参照と更新
Math::$var1 --> 42
Math::$var1 = 4423;
Math::$var1 --> 4423
メソッド経由のクラス変数の参照と更新
Math::showVar1():
self::$var1 --> 4423
Math::setVar1(123);
Math::showVar1():
self::$var1 --> 123
クラス定数の参照
Math::pi --> 3.14159265359
Math::e --> 2.71828182846
メソッド経由のクラス定数の参照と更新
Math::showPI():
self::pi --> 3.14159265359
クラス変数
プロパティ(インスタンス変数)と構文は似ていますが、キーワードstaticを伴っています。
同じクラスのメソッドから self::$変数名 の構文で参照できます(代入の左辺にもできます)。
クラス定義の外からは、クラス名::$変数名 の構文で参照できます(代入の左辺にもできます)。
クラス定数
グローバルスコープや関数スコープの定数と構文は同じですが、class定義内で定義されていることが違います。
static修飾子は不要です。
同じクラスのメソッドから self::定数名 の構文で参照できます。
代入の左辺にはできません。
クラス定義の外からは、クラス名::定数名 の構文で参照できます。
staticメソッド
メソッドの定義が static 修飾子を伴っていると、それはstaticメソッドです。
通常のメソッドは、アロー構文$インスタンス = new クラス(引数); $インスタンス->メソッド(引数)を使って呼出しますが、staticメソッドはこれに加えクラス::メソッド(引数)のインスタンスを経由しない呼出しもできます。
staticメソッドは、$thisを参照できません(インスタンス経由ではない…可能性があるので)。
staticメソッドは、selfで自分のクラスを参照できます。

このコードは、クラス定数とクラス変数についての基本的な使い方を示しています。

まず、クラス Math が定義され、クラス変数 var1 とクラス定数 pie が定義されています。var1public static で宣言されているため、クラスから直接アクセスできます。また、showVar1() メソッドと setVar1() メソッドがあり、それぞれクラス変数 var1 を参照して更新します。

まず、Math::$var1var1 の値を取得し、それを更新します。次に、showVar1() メソッドを呼び出して、クラス変数 var1 の値を表示し、setVar1() メソッドを使用して値を変更し、再度 showVar1() メソッドを呼び出して、変更された値を表示します。

次に、クラス定数 pie の値を表示し、showPI() メソッドを呼び出して、クラス定数 pi の値を表示します。

最後に、self キーワードを使用して、クラス変数とクラス定数にアクセスする方法を示しています。self::$var1 でクラス変数にアクセスし、self::pi でクラス定数にアクセスします。これらの値を echo ステートメントで出力します。

マジックメソッド

[編集]

__ からはじまるメソッドは、マジックメソッドといい処理系が特定のタイミングで暗黙に呼出します。 このまでに紹介した、コンストラクターもデストラクターもマジックメソッドです。

他にも以下のようなマジックメソッドがあります。

  • __get($name):プロパティにアクセスしようとしたときに呼び出される。
  • __set($name, $value):プロパティに代入されたときに呼び出される。
  • __isset($name):isset() 関数が、指定されたプロパティが存在するかどうかをチェックするときに呼び出される。
  • __unset($name):unset() 関数が、指定されたプロパティを破棄するときに呼び出される。
  • __call($name, $arguments):オブジェクトのメソッドが存在しない場合に呼び出される。
  • __toString():オブジェクトを文字列に変換する場合に呼び出される。
  • __invoke($arguments):オブジェクトを関数として呼び出す場合に呼び出される。

これらのマジックメソッドは、クラス内で実装することができます。マジックメソッドを使うことで、より柔軟なオブジェクト指向プログラミングを行うことができます。ただし、適切に使わないとコードが複雑になってしまう場合もあるので、注意が必要です。

__toString

[編集]

__toStringは、文字列を必要とする文脈にオブジェクトがあると呼出されます。

文字列化メソッド
<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class Hello3
{
    public function __construct(
        public readonly string $who = "World"
    ) {}
    public function __toString()
    {
        return "Hello {$this->who}!";
    }
}

$hello = new Hello3();
echo $hello, PHP_EOL;
$universe = new Hello3("Universe");
echo $universe, PHP_EOL;
実行結果
Hello World!
Hello Universe!

__get() と __set()

[編集]

__get()__set() は、PHPにおけるマジックメソッドの一種で、オブジェクトが存在しないプロパティにアクセスした際に呼び出されます。

__get() は、存在しないプロパティにアクセスした際に呼び出され、そのプロパティの値を返すために利用されます。以下は、__get() を使ったプロパティへのアクセスの例です。

<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class MyClass {
  private $data = array();

  public function __get($name) {
    return $this->data[$name];
  }
}

$obj = new MyClass();
$obj->name = 'John'; // プロパティ 'name' に 'John' を設定
echo $obj->name; // 'John' を出力

__set() は、存在しないプロパティに値を設定しようとした際に呼び出され、そのプロパティの値を設定するために用いられます。以下は、__set() を使ってプロパティに値を設定する例です。

<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class MyClass {
  private $data = array();

  public function __set($name, $value) {
    $this->data[$name] = $value;
  }
}

$obj = new MyClass();
$obj->name = 'John'; // プロパティ 'name' に 'John' を設定
echo $obj->name; // 'John' を出力

上記の例では、MyClass クラスに $data というプロパティが存在します。このプロパティは private に宣言されているため、直接アクセスすることはできません。しかし、__get() メソッドを利用することで、存在しないプロパティにアクセスした際に $data プロパティの値を返すことが可能です。また、__set() メソッドを使用することで、存在しないプロパティに値を設定し、その値を $data プロパティに保存することができます。

__get()__set() は、プロパティを動的に取得または設定するための強力な方法です。ただし、過度に使用するとコードの可読性が低下する恐れがあるため、注意が必要です。

継承

[編集]

継承は、既存のクラスを拡張し、新しいクラスを作成するための機能です。継承により、新しいクラスは既存のクラスのプロパティやメソッドを引き継ぐことができます。

以下の例では、Personクラスを継承したStudentクラスを定義しています。

<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class Person {
    protected $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    
    public function sayHello() {
        echo "Hello, my name is " . $this->name;
    }
}

class Student extends Person {
    private $studentID;
    
    public function __construct($name, $studentID) {
        parent::__construct($name);
        $this->studentID = $studentID;
    }
    
    public function getStudentID() {
        return $this->studentID;
    }
}

Personクラスは、$nameという保護されたプロパティと、__construct()sayHello()という公開されたメソッドを持っています。__construct()は、インスタンスが作成されたときに実行される特別なメソッドであり、$nameプロパティを初期化します。sayHello()は、$nameプロパティの値を使用してメッセージを出力します。

Studentクラスは、Personクラスを継承しています。Studentクラスには、$studentIDというプライベートなプロパティと、__construct()getStudentID()というパブリックなメソッドがあります。__construct()は、Personクラスの__construct()メソッドを呼び出し、$nameプロパティを初期化し、$studentIDプロパティを設定します。getStudentID()は、$studentIDプロパティの値を返します。

以下のコードを使用することで、PersonクラスとStudentクラスを使用することができます。

<?php
declare(strict_types=1);
header("Content-Type: text/plain");

$person = new Person("John");
$person->sayHello();

$student = new Student("Jane", 12345);
$student->sayHello();
echo "My student ID is " . $student->getStudentID();
上記のコードでは、Personクラスのインスタンスを作成してsayHello()メソッドを呼び出しています。
次に、Studentクラスのインスタンスを作成し、sayHello()メソッドとgetStudentID()メソッドを呼び出しています。
StudentクラスはPersonクラスを継承しているため、sayHello()メソッドを呼び出すことができます。
また、Studentクラスは、Personクラスを継承しているため、Personクラスで定義されたプロパティやメソッドを使用することができます。
例えば、Studentクラスのインスタンス$studentは、Personクラスのプロパティ$nameを持っています。
また、Studentクラスのインスタンスは、PersonクラスのメソッドsayHello()を呼び出すこともできます。
ただし、Studentクラスで定義されたプロパティやメソッドは、Personクラスのインスタンスでは使用することができません。
例えば、$student$studentIDプロパティを持っていますが、$personはそれを持っていません。

継承を使用すると、コードの再利用性が向上し、冗長なコードを減らすことができます。 また、継承を使用することで、プログラムの柔軟性が向上します。例えば、新しいクラスを作成する際に、既存のクラスを継承することで、新しいクラスが既存のクラスの機能を引き継ぐことができます。 ただし、継承を過剰に使用すると、コードの複雑さが増し、メンテナンスが困難になる場合があります。継承を使用する際には、適切に設計し、必要な機能だけを継承するように心がけることが重要です。

抽象クラス

[編集]

抽象クラスとは、具体的な実装が不完全なクラスであり、継承することによってその実装を補完することを目的としたクラスです。抽象クラスは、abstractキーワードを使用して宣言します。抽象クラス自体はインスタンス化することができず、抽象クラスを継承する具象クラスで実装する必要があります。

以下は、PHPで抽象クラスを定義する例です。

<?php
declare(strict_types=1);
header("Content-Type: text/plain");

abstract class Animal {
  protected $name;
  protected $sound;

  public function __construct($name) {
    $this->name = $name;
  }

  abstract public function makeSound();
}

class Dog extends Animal {
  public function __construct($name) {
    parent::__construct($name);
    $this->sound = "Woof!";
  }

  public function makeSound() {
    echo $this->name . " says " . $this->sound;
  }
}

class Cat extends Animal {
  public function __construct($name) {
    parent::__construct($name);
    $this->sound = "Meow!";
  }

  public function makeSound() {
    echo $this->name . " says " . $this->sound;
  }
}

$dog = new Dog("Fido");
$dog->makeSound(); // "Fido says Woof!"

$cat = new Cat("Whiskers");
$cat->makeSound(); // "Whiskers says Meow!"

上記の例では、Animalクラスを抽象クラスとして定義し、makeSound()メソッドを抽象メソッドとして宣言しています。具象クラスであるDogクラスとCatクラスは、Animalクラスを継承しています。DogクラスとCatクラスでは、Animalクラスで宣言された抽象メソッドmakeSound()を具体的に実装しています。

makeSound()メソッドは、犬が「Woof!」、猫が「Meow!」と鳴くように、各動物の鳴き声を出力します。DogクラスとCatクラスがAnimalクラスを継承しているため、Animalクラスで定義されたコンストラクタも呼び出されます。

最後に、DogクラスとCatクラスのインスタンスを作成し、makeSound()メソッドを呼び出すことによって、それぞれの動物の鳴き声を出力しています。

抽象クラスは、多くの具象クラスで共通するプロパティやメソッドを抽出し、再利用可能なコードを作成するのに役立ちます。また、抽象クラスは、継承することで必ず実装しなければならないメソッドを定義することができるため、クラスの機能や振る舞いを制限することができます。これにより、コードの信頼性や保守性を高めることができます。

例えば、動物クラスを抽象クラスとして定義した場合、多くの動物が鳴き声を出すことができるという共通点を抽出することができます。しかし、どのような鳴き声を出すかは動物によって異なります。そのため、抽象クラスでmakeSound()メソッドを定義し、具象クラスで実際の鳴き声を定義する必要があります。

また、抽象クラスは、インターフェースと似た役割を果たすことができます。インターフェースは、抽象メソッドのみを持つクラスであり、複数のインターフェースを実装することができます。一方、抽象クラスは、具象メソッドを持つことができ、単一の抽象クラスしか継承することができません。

抽象クラスは、継承することによってコードの再利用性を高めることができます。具象クラスは、抽象クラスのすべてのプロパティやメソッドを継承するため、共通の機能を持つクラスを簡単に作成することができます。また、抽象クラスは、具象クラスの実装を規定することができるため、開発者が間違った実装を行うことを防ぐことができます。

抽象クラスは、多くの具象クラスで共通する機能を抽出し、再利用可能なコードを作成することができます。また、抽象クラスは、具象クラスが必ず実装しなければならないメソッドを定義することができ、コードの信頼性や保守性を高めることができます。

トレイト

[編集]

トレイト (Trait) は、PHP5.4から導入された機能のひとつで、クラスで利用できるメソッドを、クラス継承の代わりに、再利用できるようにするための仕組みです。トレイトは、複数のクラスで共通する処理を定義することができます。

トレイトを定義するには、 trait キーワードを使います。トレイトを実装するクラスは、 use キーワードを使ってトレイトを指定します。トレイトに定義されたメソッドは、 use されたクラスのインスタンスから呼び出すことができます。

例えば、与えられた座標に移動する機能をもつ Point トレイトを定義しています。このトレイトには、コンストラクターで座標を設定する処理や、座標を文字列に変換する処理が含まれています。これらの処理は、 use するクラスで再利用することができます。

トレイトを利用することで、コードの再利用性を向上させることができます。しかし、トレイトを多用しすぎると、コードが複雑になり、メンテナンスが困難になる場合があるため、注意が必要です。

Traitの定義の例
<?php
declare(strict_types=1);

trait Point {
    public function __construct(
        private float $x = 0,
        private float $y = 0) {}

    public function __toString() : string {
        return "($this->x, $this->y)";
    }
    public function moveTo(float $dx = 0, float $dy = 0) : void {
        $this->x += $dx;
        $this->y += $dy;
    }
}

class MyPoint {
    use Point;
}

$point = new MyPoint(1.0, 2.0);
echo $point->__toString() . PHP_EOL;  // Output: (1, 2)
$point->moveTo(2.0, 3.0);
echo $point->__toString() . PHP_EOL;  // Output: (3, 5)

このコードは、トレイトを使用してクラスを構築する方法を示しています。

Point トレイトは、2つのプロパティ $x$y を持つ __construct コンストラクターと、__toString マジックメソッド、および moveTo メソッドを定義しています。このトレイトは、他のクラスに追加することができ、そのクラスに Point の機能を提供することができます。

この例では、MyPoint クラスが定義され、Point トレイトが使用されています。MyPoint クラスには、自分で追加したメソッドやプロパティがなく、Point トレイトが提供するメソッドやプロパティを使用することができます。

最後に、MyPoint クラスのインスタンス $point が作成され、初期化子によって xy の値が設定されます。次に、__toString メソッドが呼び出され、(1, 2) という文字列が表示されます。moveTo メソッドが呼び出され、xy の値が変更されます。最後に、再度 __toString メソッドが呼び出され、(3, 5) という文字列が表示されます。

インターフェイス

[編集]

インターフェイスは、クラスがどのようなメソッドを提供する必要があるかを規定するために使用されるコードの抽象化です。 インターフェイスは実装を持ちませんが、そのメソッドの名前、引数の型、戻り値の型などを指定します。

インターフェイスは、クラスの柔軟性を高めるために使用されます。複数のインターフェイスを実装することで、複数の機能を提供できます。また、複数のクラスが同じインターフェイスを実装することで、コードを共通化することができます。

インターフェイスの定義の例
interface Areable {
    public function area() : float;
}

このコードは、Areableというインターフェイスを定義しています。インターフェイスは、クラスやトレイトが実装しなければならないメソッドを定義するためのものです。

Areableインターフェイスは、area()というメソッドを持っています。このメソッドは、戻り値がfloat型であることを示しています。具体的には、面積を計算してその値を返すことが期待されています。

例えば、正方形や円など、面積を計算する必要がある様々な図形がある場合、それらの図形を表すクラスを定義するときに、Areableインターフェイスを実装することで、各図形に対してarea()メソッドを提供することができます。

トレイトとインターフェースを参照したクラス定義

[編集]

前二節で解説したトレイトとインターフェースを参照したクラス定義の例です。

図形クラス
class Shape implements Areable {
    use Point {
        Point::__construct as pointConstructor;
        Point::__toString as pointToString;
    }

    public function __construct(float $x = 0, float $y = 0) {
        $this->pointConstructor($x, $y);
    }
    public function __toString() : string {
        return $this->pointToString();
    }

    public function area() : float { return 0; }

}
$shape = new Shape(1, 2);
echo '$shape --> ', $shape, PHP_EOL;

このコードは、 Shape クラスが Areable インターフェースを実装し、 Point トレイトを使用していることを示しています。

Shape クラスのコンストラクターは、 Point トレイトで定義されたコンストラクターをオーバーライドしています。それにより、 Shape クラスが Point トレイトを使用しているときに、 Shape クラスのコンストラクターが呼び出されるようになります。

また、Shape クラスは __toString() メソッドを定義しており、これは Point トレイトで定義された同じメソッドをオーバーライドしています。これにより、 Shape オブジェクトを文字列として表現する際には、Point トレイトで定義された __toString() メソッドではなく、Shape クラスで定義された __toString() メソッドが呼び出されます。

注目したいのは、use文の中でPoint::__constructとPoint::__toStringを別名として宣言しています。 これによって、Shapeクラスが自身で実装した__constructと__toStringメソッドを持つことができます。 __constructメソッドでは、親トレイトの__constructを呼び出しています。 __toStringメソッドでは、親トレイトの__toStringを呼び出して、点の座標を文字列として返します。

最後に、 $shape オブジェクトを文字列として出力すると、 Shape クラスで定義された __toString() メソッドが呼び出され、 $shape --> (1, 2) という文字列が出力されます。

Enum

[編集]

Enumをクラスと一緒に解説するのは奇異に感じられるかもしれませんが、言語処理系としては、Enumはclassのインフラを使って実装しているので、メソッドやメソッドのアクセス記述子などの働きは同じです。

Enumの例
<?php
declare(strict_types=1);
header("Content-Type: text/plain");

// 列挙型 Seasons の定義
enum Seasons
{
    case Spring;
    case Summer;
    case Autumn;
    case Winter;

    // 漢字で名前を返すメソッド
    public function kanji()
    {
        return match ($this) {
            Seasons::Spring => "春",
            Seasons::Summer => "夏",
            Seasons::Autumn => "秋",
            Seasons::Winter => "冬",
        };
    }
}

foreach (Seasons::cases() as $season) {
    echo $season->name, " --> ", $season->kanji(), PHP_EOL;
}

このコードは、列挙型 (enum) Seasons を定義しています。

Seasons は4つの要素 Spring、Summer、Autumn、Winter を定義しています。この列挙型の各要素に対して、kanji メソッドが定義されています。kanji メソッドは、match 式を使用して、要素に対応する漢字名を返します。

もし match 式で、Seasons の列挙子を網羅していないと、 UnhandledMatchError がスローされます。これにより網羅性が担保されます。

このコードの最後では、Seasons 列挙型のすべての要素に対して、要素名と kanji メソッドの結果を出力しています。それぞれの要素名と漢字名の組み合わせが出力されます。

class と Enum の違い
Enum は、継承することもされることもできません。
Enum は、原則的にマジカルメソッドを定義できません。
Enum は、const も含めプロパティを持てません。
一見定義できても参照できません。
Enumのインスタンスは、プロパティ name で case の識別子と同じ文字列を得ることができます(書込み不可)。

UnitEnum interface

[編集]

今節で取り上げた case に値を与えない Enum を Pure Enum といいます。 Pure Enum は UnitEnum interface の Implement です[1]

interface UnitEnum
/**
 * @since 8.1
 */
interface UnitEnum
{
    public readonly string $name;

    /**
     * @return static[]
     */
    #[Pure]
    public static function cases(): array;
}
#[Pure]属性は、純粋関数(副作用を起こさない関数)であることを示します。
属性はPHP8で導入された、PHPDocのアノテーションにかわる修飾子です。

Backed Enum

[編集]

前節で紹介したEnumのように case が特定のスカラー値と結びついていない Enum を Pure Enum といいます。 これに対して、これから紹介する case にスカラー型とスカラー値を明示した Enum を Backed Enum といいます。

Backed Enumの例
<?php

enum W3C16 : int
{
    case Black   = 0x000000;
    case Glay    = 0x808080;
    case Silver  = 0xC0C0C0;
    case Blue    = 0x0000FF;
    case Navy    = 0x000080;
    case Teal    = 0x008080;
    case Green   = 0x008000;
    case Lime    = 0x00FF00;
    case Aqua    = 0x00FFFF;
    case Yellow  = 0xFFFF00;
    case Red     = 0xFF0000;
    case Fuchsia = 0xFF00FF;
    case Olive   = 0x808000;
    case Purple  = 0x800080;
    case Maroon  = 0x800000;

    public function hex() { return sprintf("#%06X", $this->value); }
}

foreach (W3C16::cases() as $colour) {
    echo $colour::class, "::$colour->name --> ", $colour->hex(), "($colour->value)", PHP_EOL;
}
echo PHP_EOL;

var_export(W3C16::from(0xFF00FF));    echo PHP_EOL;
# var_export(W3C16::from(0x606060));    echo PHP_EOL;
#  => PHP Fatal error:  Uncaught ValueError: 6316128 is not a valid backing value for enum "W3C16"
var_export(W3C16::tryFrom(0xFF00FF)); echo PHP_EOL;
var_export(W3C16::tryFrom(0x606060)); echo PHP_EOL;

?>
実行結果
W3C16::Black --> #000000(0)
W3C16::Glay --> #808080(8421504)
W3C16::Silver --> #C0C0C0(12632256)
W3C16::Blue --> #0000FF(255)
W3C16::Navy --> #000080(128)
W3C16::Teal --> #008080(32896)
W3C16::Green --> #008000(32768)
W3C16::Lime --> #00FF00(65280)
W3C16::Aqua --> #00FFFF(65535)
W3C16::Yellow --> #FFFF00(16776960)
W3C16::Red --> #FF0000(16711680)
W3C16::Fuchsia --> #FF00FF(16711935)
W3C16::Olive --> #808000(8421376)
W3C16::Purple --> #800080(8388736)
W3C16::Maroon --> #800000(8388608)

W3C16::Fuchsia
W3C16::Fuchsia
NULL

スカラー型を int と明示 この例では、W3C16 の値は int であり、明示的に int を指定しています。しかし、Enum では int 以外にも string が有効です。

異なる case に同じ値を割り当てることはできません(エラーになります)。 Enum では、異なる case に同じ値を割り当てることはできません。そのため、コード中で同じ値が割り当てられている case がある場合には、エラーが発生します。

Pure Enum の name に加え value もプロパティに持ちます。 Backed Enum では、各 case は value というプロパティを持ちます。このプロパティは、case の値を返します。また、name というプロパティも持ちます。

クラスメソッド from() は値から case を引きます。 Enum には、クラスメソッド from() があります。このメソッドを使うと、指定された値に対応する case を取得できます。ただし、無効な値を指定すると ValueError が発生します。

クラスメソッド tryFrom() は値から case を引きます。 Enum には、クラスメソッド tryFrom() もあります。このメソッドは、指定された値に対応する case を取得します。無効な値を指定すると、NULL が返されます。

Enum は、機能的には連想配列に似ていますが、Backed Enum では、各 case は一意であり、value も一意であることが保証されています。これにより、プログラマーは、例外処理などの面倒を省くことができます。一方、連想配列はキーが重複しないことが保証されますが、value が重複することがあるため、プログラマーが例外処理を実装する必要があります。


BackedEnum interface

[編集]

Backed Enum は BackedEnum interface の Implement です[2]

interface BackedEnum
/**
 * @since 8.1
 */
interface BackedEnum extends UnitEnum
{
public readonly int|string $value;

    /**
     * @param int|string $value
     * @return static
     */
    #[Pure]
    public static function from(int|string $value): static;

    /**
     * @param int|string $value
     * @return static|null
     */
    #[Pure]
    public static function tryFrom(int|string $value): ?static;
}

コードキャラリー

[編集]

まとまった規模で、実用的な目的に叶うコードを読まないと機能の存在理由などの言語設計に思い至りにくいので、コードの断片から少し踏み込んだプログラムを掲載します。

複素数クラス

[編集]

PHPには複素数がないので、クラスとして実装しました。 ただ、8.1.13 の時点ではまだ演算子オーバーロードはないので、$x->add($y) のようにメソッドで実装しました。

複素数
<?php
declare(strict_types=1);
header("Content-Type: text/plain");

class Complex
{
    public function __construct(
        private float $real = 0,
        private float $img = 0) {}

    public function __toString()
    {
        if ($this->img == 0) {
            return "$this->real";
        }
        if ($this->real == 0) {
            return "{$this->img}i";
        }
        if ($this->img < 0) {
            $neg = -$this->img;
            return "$this->real - {$neg}i";
        }
        return "$this->real + {$this->img}i";
    }

    private static function coerce($right)
    {
        switch (gettype($right)) {
            case "integer":
            case "double":
                $right = new self($right, 0);
                break;
            case "object":
                if (get_class($right) != get_class()) {
                    throw new Exception("Type mismatch", 500);
                }
                break;
            default:
                throw new Exception("Type mismatch", 500);
        }
        return $right;
    }
    public function add($right)
    {
        $right = self::coerce($right);
        return new self($this->real + $right->real, $this->img + $right->img);
    }
    public function sub($right)
    {
        $right = self::coerce($right);
        return new self($this->real - $right->real, $this->img - $right->img);
    }
    public function abs()
    {
        return sqrt($this->real * $this->real + $this->img * $this->img);
    }
}
$x = new Complex(3, 4);
echo "$x", PHP_EOL;
echo "($x)->abs() --> {$x->abs()}", PHP_EOL;
echo "$x + 10 --> {$x->add(10)}", PHP_EOL;
echo "$x + 10.0 -->  {$x->add(10.0)}", PHP_EOL;
$y = new Complex(1, 9);
echo "$x + $y --> {$x->add($y)}", PHP_EOL;
echo "$x - $y --> {$x->sub($y)}", PHP_EOL;
実行結果
3 + 4i
(3 + 4i)->abs() --> 5
3 + 4i + 10 --> 13 + 4i
3 + 4i + 10.0 -->  13 + 4i
3 + 4i + 1 + 9i --> 4 + 13i
3 + 4i - 1 + 9i --> 2 - 5i

このPHPスクリプトは、複素数を表現するComplexクラスを定義しています。複素数は、実数部分と虚数部分を持ちます。

Complexクラスのコンストラクタは、実数部分と虚数部分を初期化します。コンストラクタの引数には、float型のプロパティ$realと$imaginaryがあります。各プロパティは、引数のデフォルト値が0に設定されています。

__toString()メソッドは、Complexオブジェクトを文字列に変換するために使用されます。虚数部分が0の場合は、実数部分のみが表示されます。実数部分が0の場合は、虚数部分と文字"i"が表示されます。虚数部分が負数の場合は、負数を括弧で囲みます。それ以外の場合は、実数部分と虚数部分が+記号で連結され、文字"i"が追加されます。

coerce()メソッドは、$right引数をComplexオブジェクトに変換します。gettype()関数を使用して、$rightの型を確認します。integerまたはdoubleの場合、新しいComplexオブジェクトが作成され、$rightの値が実数部分に設定されます。objectの場合、get_class()関数を使用して、$rightがComplexオブジェクトであることを確認します。それ以外の場合、coerce()メソッドは例外をスローします。

add()メソッドとsub()メソッドは、複素数の加算と減算を行います。coerce()メソッドを使用して、引数をComplexオブジェクトに変換します。新しいComplexオブジェクトが作成され、$thisオブジェクトと$rightオブジェクトの実数部分と虚数部分が加算または減算されます。

abs()メソッドは、Complexオブジェクトの絶対値を計算します。絶対値は、複素数の実数部分の2乗と虚数部分の2乗の平方根です。

最後に、スクリプトは、Complexオブジェクトの作成と演算を実行して、結果を出力します。例えば、"$x + $y --> {$x->add($y)}"という行は、Complexオブジェクト$xと$yを加算し、結果を表示します。

図形と面積

[編集]

図形クラス(Shape)から正方形(Square)や楕円形(Oval)などを派生しています。 Shape自身は、interface Areable の実装で、Areableは共通するメソッド area()を定義しており、area() を実装しないとShapeから派生できません。

図形と面積
<?php
declare(strict_types=1);
header("Content-Type: text/plain");

trait Point {
    public function __construct(
        private float $x = 0,
        private float $y = 0) {}

    public function __toString() : string {
        return "($this->x, $this->y)";
    }
    public function moveTo(float $dx = 0, float $dy = 0) : void {
        $this->x += $dx;
        $this->y += $dy;
    }
}

interface Areable {
    public function area() : float;
}

class Shape implements Areable {
    use Point {
        Point::__construct as pointConstructor;
        Point::__toString as pointToString;
    }

    public function __construct(float $x = 0, float $y = 0) {
        $this->pointConstructor($x, $y);
    }
    public function __toString() : string {
        return $this->pointToString();
    }

    public function area() : float { return 0; }
}

$shape = new Shape(1, 2);
echo '$shape --> ', $shape, PHP_EOL;

class Square extends Shape {
    public function __construct($x, $y, private float $wh = 0) {
        parent::__construct($x, $y);
    }
    public function __toString() : string {
        return "[$this->wh] + " . parent::__toString();
    }
    public function area() : float { return $this->wh * $this->wh; }
}
$square = new Square(1, 2, 3);
echo '$square --> ', $square, PHP_EOL;

class Rectangle extends Shape {
    public function __construct($x, $y, private float $width = 0, private float $height = 0) {
        parent::__construct($x, $y);
    }
    public function __toString() : string {
        return "[$this->width x $this->height] + " . parent::__toString();
    }
    public function area() : float { return $this->width * $this->height; }
}
$rectangle = new Rectangle(1, 2, 3, 4);
echo '$rectangle --> ', $rectangle, PHP_EOL;

class Circle extends Shape {
    public function __construct($x, $y, private float $radius = 0) {
        parent::__construct($x, $y);
    }
    public function __toString() :string {
        return "($this->radius) + " . parent::__toString();
    }
    public function area() : float { return 3.14159265359 * $this->radius * $this->radius; }
}
$circle = new Circle(8, 9, 10);
echo '$circle --> ', $circle, PHP_EOL;

class Oval extends Shape {
    public function __construct($x, $y, private float $width = 0, private float $height = 0) {
        parent::__construct($x, $y);
    }
    public function __toString() : string {
        return "($this->width x $this->height) + " . parent::__toString();
    }
    public function area() : float { return 3.14159265359 * $this->width * $this->height / 4; }
}
$oval = new Oval(1, 2, 3, 4);
echo '$oval --> ', $oval, PHP_EOL;
$oval->moveTo(10, 20);
echo '$oval --> ', $oval, PHP_EOL;

const shapes = array(
    new Square(0,1,2),
    new Rectangle(3,4,5,6),
    new Circle(8,9,10),
    new Oval(3,4,5,6)
    );
foreach(shapes as $shape) {
    echo get_class($shape),":", $shape, ",\tarea:", $shape->area(), PHP_EOL;
}
実行結果
$shape --> (1, 2)
$square --> [3] + (1, 2)
$rectangle --> [3 x 4] + (1, 2)
$circle --> (10) + (8, 9)
$oval --> (3 x 4) + (1, 2)
$oval --> (3 x 4) + (11, 22)
Square:[2] + (0, 1),	area:4
Rectangle:[5 x 6] + (3, 4),	area:30
Circle:(10) + (8, 9),	area:314.159265359
Oval:(5 x 6) + (3, 4),	area:23.561944901925

このPHPコードは、幾何学的な形状(正方形、長方形、円、楕円)を表すために、トレイト(trait)を使用したオブジェクト指向プログラミングの例を示しています。

まず、Pointというトレイトを定義しています。このトレイトは、x座標とy座標を表すプロパティと、座標を移動するmoveToメソッドを持っています。

次に、Areableというインターフェイスを定義しています。このインターフェイスには、面積を返すareaメソッドが含まれています。

そして、Shapeクラスを定義しています。このクラスは、Pointトレイトを使用して、x座標とy座標を持っています。また、Areableインターフェイスを実装しており、areaメソッドをオーバーライドして0を返すようにしています。このクラスは、正方形、長方形、円、楕円の基底クラスとして使用されます。

Square、Rectangle、Circle、Ovalクラスは、Shapeクラスを拡張しています。それぞれが異なるプロパティを持っており、それぞれが面積を計算するためにareaメソッドをオーバーライドしています。

最後に、いくつかの形状のインスタンスを作成し、それらの面積を計算して表示するためのコードがあります。また、PointトレイトのmoveToメソッドを使用して、Ovalの座標を変更しています。

このプログラムは、オブジェクト指向プログラミングの基本的な概念を示しています。特に、クラスの継承、トレイトの使用、インターフェイスの実装などが重要な要素です。また、オブジェクトの状態を変更するメソッド(moveTo)を定義しています。

用語集

[編集]
  1. オブジェクト指向プログラミング(OOP): ソフトウェア設計の一種で、プログラムを「オブジェクト」と呼ばれる単位に分割し、それぞれのオブジェクトがデータと処理を組み合わせた「クラス」という型で表現される方法。OOPにはカプセル化、継承、ポリモーフィズムの3つの重要な概念が含まれている。
  2. クラス: OOPで、オブジェクトの型を定義するためのテンプレート。クラスには、属性(メンバ変数)とメソッド(メンバ関数)が含まれる。
  3. オブジェクト: クラスのインスタンスであり、データと処理を組み合わせた単位。オブジェクトは、それぞれ独自の状態を持つことができ、同じクラスから作成された他のオブジェクトとは異なる動作をすることができる。
  4. 属性: クラスに定義されたメンバ変数のこと。オブジェクトには、そのクラスに定義された属性が含まれる。
  5. メソッド: クラスに定義されたメンバ関数のこと。オブジェクトには、そのクラスに定義されたメソッドが含まれる。
  6. カプセル化: クラスの属性とメソッドを一体化し、外部からのアクセスを制限する機能。カプセル化により、クラスの実装の詳細を隠蔽することができる。
  7. 継承: クラスの再利用性を高めるための機能で、既存のクラスを基にして新しいクラスを作成することができる。継承元のクラスを「スーパークラス」、継承したクラスを「サブクラス」と呼ぶ。
  8. ポリモーフィズム: 同じインターフェースを持つ異なるクラスのオブジェクトが同じメソッドを呼び出すことができる機能。ポリモーフィズムにより、クラスの階層構造を自然に表現することができる。
  9. インスタンス: クラスから生成されたオブジェクトのこと。同じクラスから生成されたオブジェクトでも、それぞれ異なる状態を持つ。
  10. アクセス修飾子: プロパティやメソッドに対して、外部からのアクセスの制限を設定するためのキーワード。public、private、protectedの3つがある。
  11. 抽象クラス: インスタンスを生成できないクラスのこと。抽象メソッドを持ち、継承して実装することを前提としている。
  12. 抽象メソッド: 実装されていないメソッドのこと。抽象クラスで定義され、サブクラスで実装することを期待されている。
  13. インターフェース: 抽象クラスと似た概念だが、抽象メソッドのみを持ち、実装を強制することができる。
  14. 多態性: 同じメソッドが、異なるクラスで異なる振る舞いをすることができる機能のこと。継承やインターフェースを用いて実現される。
  1. ^ The UnitEnum interface
  2. ^ The BackedEnum interface