オブジェクト指向プログラミング入門

「OOP」という用語はオブジェクト指向プログラミングを指し、これはコードを整理し構造化する方法です。OOPでは、プログラムを一連のコマンドや関数の代わりに、互いに通信するオブジェクトの集合として見ることができます。

OOPにおいて、「オブジェクト」とは、データとそのデータを操作する関数を含む単位です。オブジェクトは「クラス」に基づいて作成され、クラスはオブジェクトの設計図やテンプレートと考えることができます。クラスがあれば、その「インスタンス」を作成できます。これは、そのクラスに基づいて作成された具体的なオブジェクトです。

PHPで簡単なクラスを作成する方法を見てみましょう。クラスを定義する際には、キーワード「class」を使用し、その後にクラス名、そしてクラスの関数(「メソッド」と呼ばれる)と変数(「プロパティ」と呼ばれる)を囲む波括弧が続きます:

class Auto
{
	function honk() // zatrub -> honk
	{
		echo 'Bip bip!';
	}
}

この例では、Autoという名前のクラスを作成し、honkという名前の関数(または「メソッド」)を1つ含んでいます。

各クラスは、1つの主要なタスクのみを解決する必要があります。クラスが多くのことを行いすぎる場合は、より小さく、特化したクラスに分割するのが適切かもしれません。

コードを整理し、ナビゲートしやすくするために、通常、クラスは別々のファイルに保存します。ファイル名はクラス名に対応する必要があるため、Autoクラスの場合、ファイル名はAuto.phpになります。

クラスに名前を付ける際には、「PascalCase」という規則に従うのが良いでしょう。これは、名前の各単語が大文字で始まり、アンダースコアや他の区切り文字がないことを意味します。メソッドとプロパティは「camelCase」規則を使用します。これは、小文字で始まることを意味します。

PHPの一部のメソッドには特別な役割があり、__(2つのアンダースコア)のプレフィックスでマークされています。最も重要な特殊メソッドの1つは「コンストラクタ」であり、__constructとしてマークされています。コンストラクタは、クラスの新しいインスタンスを作成するときに自動的に呼び出されるメソッドです。

コンストラクタは、オブジェクトの初期状態を設定するためによく使用されます。例えば、人を表すオブジェクトを作成する場合、コンストラクタを使用してその年齢、名前、またはその他のプロパティを設定できます。

PHPでコンストラクタを使用する方法を見てみましょう:

class Person // Osoba -> Person
{
	private $age; // vek -> age

	function __construct($age) // vek -> age
	{
		$this->age = $age; // vek -> age
	}

	function getAge() // kolikJeTiLet -> getAge
	{
		return $this->age; // vek -> age
	}
}

$person = new Person(25); // osoba -> person, Osoba -> Person
echo $person->getAge(); // 出力: 25 // osoba -> person, kolikJeTiLet -> getAge, Vypíše: 25 -> 出力: 25

この例では、Personクラスにはプロパティ(変数)$ageがあり、さらにこのプロパティを設定するコンストラクタがあります。メソッドgetAge()は、人の年齢にアクセスすることを可能にします。

疑似変数$thisは、クラス内でオブジェクトのプロパティやメソッドにアクセスするために使用されます。

キーワードnewは、クラスの新しいインスタンスを作成するために使用されます。上記の例では、年齢25歳の新しい人を作成しました。

オブジェクト作成時に指定されない場合、コンストラクタのパラメータにデフォルト値を設定することもできます。例えば:

class Person // Osoba -> Person
{
	private $age; // vek -> age

	function __construct($age = 20) // vek -> age
	{
		$this->age = $age; // vek -> age
	}

	function getAge() // kolikJeTiLet -> getAge
	{
		return $this->age; // vek -> age
	}
}

$person = new Person;  // 引数を渡さない場合は括弧を省略できます // osoba -> person, Osoba -> Person, pokud nepředáváme žádný argument, lze závorky vynechat -> 引数を渡さない場合は括弧を省略できます
echo $person->getAge(); // 出力: 20 // osoba -> person, kolikJeTiLet -> getAge, Vypíše: 20 -> 出力: 20

この例では、Personオブジェクトを作成する際に年齢を指定しない場合、デフォルト値20が使用されます。

嬉しいことに、プロパティの定義とそのコンストラクタによる初期化は、このように短縮および簡略化できます:

class Person // Osoba -> Person
{
	function __construct(
		private $age = 20, // vek -> age
	) {
	}
}

完全を期すために、コンストラクタに加えて、オブジェクトにはデストラクタ(メソッド __destruct)もあり、これはオブジェクトがメモリから解放される前に呼び出されます。

名前空間

名前空間(英語では「namespaces」)を使用すると、関連するクラス、関数、定数を整理してグループ化し、同時に名前の衝突を回避できます。これらはコンピュータのフォルダのようなものと考えることができ、各フォルダには特定のプロジェクトやテーマに属するファイルが含まれています。

名前空間は、大規模なプロジェクトや、クラス名の衝突が発生する可能性のあるサードパーティのライブラリを使用する場合に特に役立ちます。

プロジェクトにAutoという名前のクラスがあり、それをTransportという名前の名前空間に配置したいと想像してください。(Doprava → Transport) 次のようにします:

namespace Transport; // Doprava -> Transport

class Auto
{
	function honk() // zatrub -> honk
	{
		echo 'Bip bip!';
	}
}

別のファイルでAutoクラスを使用したい場合は、クラスがどの名前空間から来ているかを指定する必要があります:

$auto = new Transport\Auto; // Doprava -> Transport

簡略化のために、ファイルの先頭で使用したい特定の名前空間のクラスを指定できます。これにより、完全なパスを指定せずにインスタンスを作成できます:

use Transport\Auto; // Doprava -> Transport

$auto = new Auto;

継承

継承はオブジェクト指向プログラミングのツールであり、既存のクラスに基づいて新しいクラスを作成し、そのプロパティやメソッドを引き継ぎ、必要に応じて拡張または再定義することができます。継承により、コードの再利用性とクラス階層が保証されます。

簡単に言えば、1つのクラスがあり、それから派生した別のクラスをいくつかの変更を加えて作成したい場合、元のクラスから新しいクラスを「継承」できます。

PHPでは、キーワードextendsを使用して継承を実現します。

私たちのPersonクラスは年齢に関する情報を保持しています。Personを拡張し、研究分野に関する情報を追加する別のクラスStudentを持つことができます。

例を見てみましょう:

class Person // Osoba -> Person
{
	private $age; // vek -> age

	function __construct($age) // vek -> age
	{
		$this->age = $age; // vek -> age
	}

	function displayInfo() // vypisInformace -> displayInfo
	{
		echo "年齢: {$this->age} 歳\n"; // Věk: {$this->vek} let\n -> 年齢: {$this->age} 歳\n
	}
}

class Student extends Person // Osoba -> Person
{
	private $major; // obor -> major

	function __construct($age, $major) // vek -> age, obor -> major
	{
		parent::__construct($age); // vek -> age
		$this->major = $major; // obor -> major
	}

	function displayInfo() // vypisInformace -> displayInfo
	{
		parent::displayInfo(); // vypisInformace -> displayInfo
		echo "専攻: {$this->major} \n"; // Obor studia: {$this->obor} \n -> 専攻: {$this->major} \n
	}
}

$student = new Student(20, '情報学'); // Informatika -> 情報学
$student->displayInfo(); // vypisInformace -> displayInfo

このコードはどのように機能しますか?

  • キーワードextendsを使用してPersonクラスを拡張しました。これは、StudentクラスがPersonからすべてのメソッドとプロパティを継承することを意味します。
  • キーワードparent::を使用すると、親クラスのメソッドを呼び出すことができます。この場合、Studentクラスに独自の機能を追加する前に、Personクラスからコンストラクタを呼び出しました。そして同様に、学生に関する情報を表示する前に、祖先のメソッドdisplayInfo()を呼び出しました。

継承は、クラス間に「is-a」関係が存在する状況を対象としています。例えば、StudentPersonです。猫は動物です。これにより、コードで1つのオブジェクト(例:「Person」)を期待する場合に、代わりに継承されたオブジェクト(例:「Student」)を使用する可能性が得られます。

継承の主な目的はコードの重複を防ぐことではないことを認識することが重要です。逆に、継承の不適切な使用は、複雑で保守が困難なコードにつながる可能性があります。クラス間に「is-a」関係が存在しない場合は、継承の代わりにコンポジションを検討する必要があります。

PersonクラスとStudentクラスのdisplayInfo()メソッドが少し異なる情報を表示することに注意してください。そして、このメソッドの他の実装を提供する追加のクラス(例えばEmployee)を追加できます。(Zamestnanec → Employee) 異なるクラスのオブジェクトが同じメソッドに異なる方法で応答する能力は、ポリモーフィズムと呼ばれます:

$people = [ // osoby -> people
	new Person(30), // Osoba -> Person
	new Student(20, '情報学'), // Informatika -> 情報学
	new Employee(45, 'ディレクター'), // Zamestnanec -> Employee, Ředitel -> ディレクター
];

foreach ($people as $person) { // osoby -> people, osoba -> person
	$person->displayInfo(); // osoba -> person, vypisInformace -> displayInfo
}

コンポジション

コンポジションは、他のクラスのプロパティやメソッドを継承する代わりに、単にそのインスタンスを自分のクラス内で利用するテクニックです。これにより、複雑な継承構造を作成することなく、複数のクラスの機能とプロパティを組み合わせることができます。

例を見てみましょう。EngineクラスとCarクラスがあります。(Motor → Engine, Auto → Car) 「車はエンジンである」と言う代わりに、「車はエンジンを持つ」と言います。これは典型的なコンポジションの関係です。

class Engine // Motor -> Engine
{
	function start() // zapni -> start
	{
		echo 'エンジンが作動しています。'; // Motor běží. -> エンジンが作動しています。
	}
}

class Car // Auto -> Auto
{
	private $engine; // motor -> engine

	function __construct()
	{
		$this->engine = new Engine; // motor -> engine, Motor -> Engine
	}

	function start()
	{
		$this->engine->start(); // motor -> engine, zapni -> start
		echo '車は走行準備ができました!'; // Auto je připraveno k jízdě! -> 車は走行準備ができました!
	}
}

$car = new Car; // auto -> car, Auto -> Car
$car->start(); // auto -> car

ここでは、CarEngineのすべてのプロパティとメソッドを持っているわけではありませんが、プロパティ$engineを通じてそれにアクセスできます。

コンポジションの利点は、設計の柔軟性が高く、将来の変更の可能性が向上することです。

可視性

PHPでは、クラスのプロパティ、メソッド、定数に対して「可視性」を定義できます。可視性は、これらの要素にどこからアクセスできるかを決定します。

  1. Public: 要素がpublicとしてマークされている場合、クラス外からでもどこからでもアクセスできることを意味します。
  2. Protected: protectedとマークされた要素は、そのクラス内およびそのすべての子孫(このクラスから継承するクラス)からのみアクセス可能です。
  3. Private: 要素がprivateの場合、それが定義されたクラス内からのみアクセスできます。

可視性を指定しない場合、PHPは自動的にpublicに設定します。

サンプルコードを見てみましょう:

class VisibilityDemo // UkazkaViditelnosti -> VisibilityDemo
{
	public $publicProperty = 'Public'; // verejnaVlastnost -> publicProperty, Veřejná -> Public
	protected $protectedProperty = 'Protected'; // chranenaVlastnost -> protectedProperty, Chráněná -> Protected
	private $privateProperty = 'Private'; // soukromaVlastnost -> privateProperty, Soukromá -> Private

	public function displayProperties() // vypisVlastnosti -> displayProperties
	{
		echo $this->publicProperty;  // 動作します // verejnaVlastnost -> publicProperty, Funguje -> 動作します
		echo $this->protectedProperty; // 動作します // chranenaVlastnost -> protectedProperty, Funguje -> 動作します
		echo $this->privateProperty; // 動作します // soukromaVlastnost -> privateProperty, Funguje -> 動作します
	}
}

$object = new VisibilityDemo; // objekt -> object, UkazkaViditelnosti -> VisibilityDemo
$object->displayProperties(); // vypisVlastnosti -> displayProperties
echo $object->publicProperty;      // 動作します // verejnaVlastnost -> publicProperty, Funguje -> 動作します
// echo $object->protectedProperty;  // エラーをスローします // chranenaVlastnost -> protectedProperty, Vyhodí chybu -> エラーをスローします
// echo $object->privateProperty;  // エラーをスローします // soukromaVlastnost -> privateProperty, Vyhodí chybu -> エラーをスローします

クラスの継承を続けます:

class ChildClass extends VisibilityDemo // PotomekTridy -> ChildClass, UkazkaViditelnosti -> VisibilityDemo
{
	public function displayProperties() // vypisVlastnosti -> displayProperties
	{
		echo $this->publicProperty;   // 動作します // verejnaVlastnost -> publicProperty, Funguje -> 動作します
		echo $this->protectedProperty;  // 動作します // chranenaVlastnost -> protectedProperty, Funguje -> 動作します
		// echo $this->privateProperty;  // エラーをスローします // soukromaVlastnost -> privateProperty, Vyhodí chybu -> エラーをスローします
	}
}

この場合、ChildClassクラスのdisplayProperties()メソッドは、パブリックおよびプロテクテッドなプロパティにアクセスできますが、親クラスのプライベートなプロパティにはアクセスできません。

データとメソッドは可能な限り隠蔽し、定義されたインターフェースを通じてのみアクセス可能であるべきです。これにより、コードの残りの部分に影響を与えることなく、クラスの内部実装を変更できます。

finalキーワード

PHPでは、クラス、メソッド、または定数が継承またはオーバーライドされるのを防ぎたい場合に、finalキーワードを使用できます。クラスをfinalとしてマークすると、拡張できません。メソッドをfinalとしてマークすると、子クラスでオーバーライドできません。

特定のクラスやメソッドがさらに変更されないことを知っていると、潜在的な競合を心配することなく、変更をより簡単に行うことができます。例えば、どの子孫もすでに同じ名前のメソッドを持っていて衝突が発生するという心配なしに、新しいメソッドを追加できます。または、メソッドのパラメータを変更することもできます。なぜなら、子孫でオーバーライドされたメソッドとの不整合を引き起こすリスクがないからです。

final class FinalClass // FinalniTrida -> FinalClass
{
}

// 次のコードはエラーをスローします。finalクラスからは継承できないためです。 // Následující kód vyvolá chybu, protože nemůžeme zdědit od finalní třídy. -> 次のコードはエラーをスローします。finalクラスからは継承できないためです。
class ChildOfFinalClass extends FinalClass // PotomekFinalniTridy -> ChildOfFinalClass, FinalniTrida -> FinalClass
{
}

この例では、finalクラスFinalClassからの継承の試みはエラーをスローします。

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

PHPでクラスの「静的」要素について話すとき、それは特定のクラスインスタンスではなく、クラス自体に属するメソッドとプロパティを意味します。これは、それらにアクセスするためにクラスのインスタンスを作成する必要がないことを意味します。代わりに、クラス名を介して直接呼び出したりアクセスしたりします。

静的要素はクラスに属し、そのインスタンスには属さないため、静的メソッド内で疑似変数$thisを使用することはできないことに注意してください。

静的プロパティの使用は落とし穴だらけの不明瞭なコードにつながるため、決して使用すべきではなく、ここでは使用例も示しません。対照的に、静的メソッドは便利です。使用例:

class Calculator // Kalkulator -> Calculator
{
	public static function add($a, $b) // scitani -> add
	{
		return $a + $b;
	}

	public static function subtract($a, $b) // odecitani -> subtract
	{
		return $a - $b;
	}
}

// クラスインスタンスを作成せずに静的メソッドを使用 // Použití statické metody bez vytvoření instance třídy -> クラスインスタンスを作成せずに静的メソッドを使用
echo Calculator::add(5, 3); // 結果: 8 // Kalkulator -> Calculator, scitani -> add, Výsledek: 8 -> 結果: 8
echo Calculator::subtract(5, 3); // 結果: 2 // Kalkulator -> Calculator, odecitani -> subtract, Výsledek: 2 -> 結果: 2

この例では、2つの静的メソッドを持つCalculatorクラスを作成しました。これらのメソッドは、::演算子を使用してクラスのインスタンスを作成せずに直接呼び出すことができます。静的メソッドは、特定のクラスインスタンスの状態に依存しない操作に特に役立ちます。

クラス定数

クラス内で定数を定義するオプションがあります。定数は、プログラムの実行中に決して変更されない値です。変数とは異なり、定数の値は常に同じままです。

class Car // Auto -> Car
{
	public const NumberOfWheels = 4; // PocetKol -> NumberOfWheels

	public function displayNumberOfWheels(): int // zobrazPocetKol -> displayNumberOfWheels
	{
		echo self::NumberOfWheels; // PocetKol -> NumberOfWheels
	}
}

echo Car::NumberOfWheels;  // 出力: 4 // Auto -> Car, PocetKol -> NumberOfWheels, Výstup: 4 -> 出力: 4

この例では、定数NumberOfWheelsを持つCarクラスがあります。クラス内で定数にアクセスしたい場合は、クラス名の代わりにキーワードselfを使用できます。

オブジェクトインターフェース

オブジェクトインターフェースは、クラスの「契約」として機能します。クラスがオブジェクトインターフェースを実装する場合、そのインターフェースが定義するすべてのメソッドを含まなければなりません。これは、特定のクラスが同じ「契約」または構造に従うことを保証するための優れた方法です。

PHPでは、インターフェースはキーワードinterfaceで定義されます。インターフェースで定義されたすべてのメソッドはパブリック(public)です。クラスがインターフェースを実装する場合、キーワードimplementsを使用します。

interface Animal // Zvire -> Animal
{
	function makeSound(); // vydejZvuk -> makeSound
}

class Cat implements Animal // Kocka -> Cat, Zvire -> Animal
{
	public function makeSound() // vydejZvuk -> makeSound
	{
		echo 'ニャー'; // Mňau -> ニャー
	}
}

$cat = new Cat; // kocka -> cat, Kocka -> Cat
$cat->makeSound(); // kocka -> cat, vydejZvuk -> makeSound

クラスがインターフェースを実装しても、期待されるすべてのメソッドが定義されていない場合、PHPはエラーをスローします。

クラスは一度に複数のインターフェースを実装できます。これは、クラスが1つのクラスからしか継承できない継承とは異なります:

interface Guard // Hlidac -> Guard
{
	function guardHouse(); // hlidejDum -> guardHouse
}

class Dog implements Animal, Guard // Pes -> Dog, Zvire -> Animal, Hlidac -> Guard
{
	public function makeSound() // vydejZvuk -> makeSound
	{
		echo 'ワン'; // Haf -> ワン
	}

	public function guardHouse() // hlidejDum -> guardHouse
	{
		echo '犬は家を注意深く見守っています'; // Pes bedlivě střeží dům -> 犬は家を注意深く見守っています
	}
}

抽象クラス

抽象クラスは他のクラスの基本テンプレートとして機能しますが、直接インスタンスを作成することはできません。これらは、完全なメソッドと、内容が定義されていない抽象メソッドの組み合わせを含んでいます。抽象クラスから継承するクラスは、祖先のすべての抽象メソッドの定義を提供する必要があります。

抽象クラスを定義するには、キーワードabstractを使用します。

abstract class AbstractClass // AbstraktniTrida -> AbstractClass
{
	public function regularMethod() // obycejnaMetoda -> regularMethod
	{
		echo 'これは通常のメソッドです'; // Toto je obyčejná metoda -> これは通常のメソッドです
	}

	abstract public function abstractMethod(); // abstraktniMetoda -> abstractMethod
}

class Child extends AbstractClass // Potomek -> Child, AbstraktniTrida -> AbstractClass
{
	public function abstractMethod() // abstraktniMetoda -> abstractMethod
	{
		echo 'これは抽象メソッドの実装です'; // Toto je implementace abstraktní metody -> これは抽象メソッドの実装です
	}
}

$instance = new Child; // Potomek -> Child
$instance->regularMethod(); // obycejnaMetoda -> regularMethod
$instance->abstractMethod(); // abstraktniMetoda -> abstractMethod

この例では、1つの通常メソッドと1つの抽象メソッドを持つ抽象クラスがあります。次に、AbstractClassから継承し、抽象メソッドの実装を提供するChildクラスがあります。

インターフェースと抽象クラスは実際にはどのように異なりますか?抽象クラスは抽象メソッドと具象メソッドの両方を含むことができますが、インターフェースはクラスが実装しなければならないメソッドを定義するだけで、実装は提供しません。クラスは1つの抽象クラスからしか継承できませんが、任意の数のインターフェースを実装できます。

型チェック

プログラミングでは、扱っているデータが正しい型であることを確認することが非常に重要です。PHPには、これを保証するためのツールがあります。データが正しい型を持っているかどうかを確認することは、「型チェック」と呼ばれます。

PHPで遭遇する可能性のある型:

  1. 基本型: int(整数)、float(浮動小数点数)、bool(真偽値)、string(文字列)、array(配列)、nullが含まれます。
  2. クラス: 値が特定のクラスのインスタンスであることを要求する場合。
  3. インターフェース: クラスが実装しなければならないメソッドのセットを定義します。インターフェースを満たす値は、これらのメソッドを持っている必要があります。
  4. 混合型: 変数が複数の許可された型を持つことができるように指定できます。
  5. Void: この特殊な型は、関数またはメソッドが値を返さないことを示します。

型を含むようにコードを修正する方法を見てみましょう:

class Person // Osoba -> Person
{
	private int $age; // vek -> age

	public function __construct(int $age) // vek -> age
	{
		$this->age = $age; // vek -> age
	}

	public function displayAge(): void // vypisVek -> displayAge
	{
		echo "この人は{$this->age}歳です。"; // Této osobě je {$this->vek} let. -> この人は{$this->age}歳です。
	}
}

/**
 * Personクラスのオブジェクトを受け取り、その人の年齢を表示する関数。 // Funkce, která přijímá objekt třídy Osoba a vypíše věk osoby. -> Personクラスのオブジェクトを受け取り、その人の年齢を表示する関数。
 */
function displayPersonAge(Person $person): void // vypisVekOsoby -> displayPersonAge, osoba -> person, Osoba -> Person
{
	$person->displayAge(); // osoba -> person, vypisVek -> displayAge
}

このようにして、コードが正しい型のデータを期待し、それを使用して動作することを保証し、潜在的なエラーを防ぐのに役立ちます。

PHPでは直接記述できない型もあります。その場合、phpDocコメントで指定されます。これは、/**で始まり*/で終わるPHPコードを文書化するための標準形式です。これにより、クラス、メソッドなどの説明を追加できます。また、いわゆるアノテーション@var@param@returnを使用して複雑な型を指定することもできます。これらの型は、静的コード解析ツールによって使用されますが、PHP自体はそれらをチェックしません。

class List // Seznam -> List
{
	/** @var array<Person>  この表記は、Personオブジェクトの配列であることを示します */ // Osoba -> Person, zápis říká, že jde o pole objektů Osoba -> この表記は、Personオブジェクトの配列であることを示します
	private array $people = []; // osoby -> people

	public function addPerson(Person $person): void // pridatOsobu -> addPerson, osoba -> person, Osoba -> Person
	{
		$this->people[] = $person; // osoby -> people, osoba -> person
	}
}

比較と同一性

PHPでは、2つの方法でオブジェクトを比較できます:

  1. 値の比較 ==: オブジェクトが同じクラスであり、プロパティに同じ値を持っているかどうかを確認します。
  2. 同一性 ===: 同じオブジェクトインスタンスであるかどうかを確認します。
class Car // Auto -> Car
{
	public string $brand; // znacka -> brand

	public function __construct(string $brand) // znacka -> brand
	{
		$this->brand = $brand; // znacka -> brand
	}
}

$auto1 = new Car('Skoda'); // Auto -> Car
$auto2 = new Car('Skoda'); // Auto -> Car
$auto3 = $auto1;

var_dump($auto1 == $auto2);   // true、同じ値を持っているため // true, protože mají stejnou hodnotu -> true、同じ値を持っているため
var_dump($auto1 === $auto2);  // false、同じインスタンスではないため // false, protože nejsou stejná instance -> false、同じインスタンスではないため
var_dump($auto1 === $auto3);  // true、$auto3は$auto1と同じインスタンスであるため // true, protože $auto3 je stejná instance jako $auto1 -> true、$auto3は$auto1と同じインスタンスであるため

instanceof 演算子

instanceof演算子を使用すると、特定のオブジェクトが特定のクラスのインスタンスであるか、そのクラスの子孫であるか、または特定のインターフェースを実装しているかどうかを確認できます。

Personクラスと、Personクラスの子孫である別のクラスStudentがあると想像してみましょう:

class Person // Osoba -> Person
{
	private int $age; // vek -> age

	public function __construct(int $age) // vek -> age
	{
		$this->age = $age; // vek -> age
	}
}

class Student extends Person // Osoba -> Person
{
	private string $major; // obor -> major

	public function __construct(int $age, string $major) // vek -> age, obor -> major
	{
		parent::__construct($age); // vek -> age
		$this->major = $major; // obor -> major
	}
}

$student = new Student(20, '情報学'); // Informatika -> 情報学

// $studentがStudentクラスのインスタンスであるかどうかの確認 // Ověření, zda je $student instancí třídy Student -> $studentがStudentクラスのインスタンスであるかどうかの確認
var_dump($student instanceof Student);  // 出力: bool(true) // Výstup: bool(true) -> 出力: bool(true)

// $studentがPersonクラスのインスタンスであるかどうかの確認(StudentはPersonの子孫であるため) // Ověření, zda je $student instancí třídy Osoba (protože Student je potomek Osoba) -> $studentがPersonクラスのインスタンスであるかどうかの確認(StudentはPersonの子孫であるため)
var_dump($student instanceof Person);     // 出力: bool(true) // Výstup: bool(true) -> 出力: bool(true)

出力から、$studentオブジェクトは同時に両方のクラス(StudentPerson)のインスタンスと見なされることが明らかです。

Fluent Interface

「Fluent Interface」(英語では「Fluent Interface」)は、OOPのテクニックであり、1回の呼び出しでメソッドを連鎖させることができます。これにより、コードがしばしば簡略化され、明確になります。

Fluent Interfaceの重要な要素は、チェーン内の各メソッドが現在のオブジェクトへの参照を返すことです。これは、メソッドの最後にreturn $this;を使用することで実現します。このプログラミングスタイルは、オブジェクトのプロパティ値を設定する「セッター」と呼ばれるメソッドとよく関連付けられます。

電子メール送信の例でFluent Interfaceがどのように見えるかを示します:

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

この例では、メソッドsetFrom()setRecipient()setMessage()は、対応する値(送信者、受信者、メッセージの内容)を設定するために使用されます。これらの各値を設定した後、メソッドは現在のオブジェクト($email)を返し、これにより次のメソッドを連鎖させることができます。最後に、実際に電子メールを送信するメソッドsend()を呼び出します。

Fluent Interfaceのおかげで、直感的で読みやすいコードを書くことができます。

cloneを使用したコピー

PHPでは、clone演算子を使用してオブジェクトのコピーを作成できます。この方法で、同一の内容を持つ新しいインスタンスを取得します。

オブジェクトをコピーする際に一部のプロパティを変更する必要がある場合は、クラス内で特殊なメソッド__clone()を定義できます。このメソッドは、オブジェクトがクローンされるときに自動的に呼び出されます。

class Sheep // Ovce -> Sheep
{
	public string $name; // jmeno -> name

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

	public function __clone()
	{
		$this->name = 'クローン ' . $this->name; // jmeno -> name, Klon -> クローン
	}
}

$original = new Sheep('Dolly'); // Ovce -> Sheep
echo $original->name . "\n";  // 出力: Dolly // jmeno -> name, Vypíše: Dolly -> 出力: Dolly

$clone = clone $original; // klon -> clone
echo $clone->name . "\n";      // 出力: クローン Dolly // klon -> clone, jmeno -> name, Vypíše: Klon Dolly -> 出力: クローン Dolly

この例では、1つのプロパティ$nameを持つSheepクラスがあります。このクラスのインスタンスをクローンすると、__clone()メソッドは、クローンされた羊の名前が「クローン」プレフィックスを取得するようにします。

トレイト

PHPのトレイトは、クラス間でメソッド、プロパティ、定数を共有し、コードの重複を防ぐことを可能にするツールです。これらは「コピー&ペースト」(Ctrl-CおよびCtrl-V)メカニズムのようなものと考えることができ、トレイトの内容がクラスに「挿入」されます。これにより、複雑なクラス階層を作成することなくコードを再利用できます。

PHPでトレイトを使用する方法の簡単な例を示しましょう:

trait Honking // Troubeni -> Honking
{
	public function honk() // zatrub -> honk
	{
		echo 'Bip bip!';
	}
}

class Car // Auto -> Car
{
	use Honking; // Troubeni -> Honking
}

class Truck // Nakladak -> Truck
{
	use Honking; // Troubeni -> Honking
}

$car = new Car; // auto -> car, Auto -> Car
$car->honk(); // 'Bip bip!' を出力 // auto -> car, zatrub -> honk, Vypíše 'Bip bip!' -> 'Bip bip!' を出力

$truck = new Truck; // nakladak -> truck, Nakladak -> Truck
$truck->honk(); // 同様に 'Bip bip!' を出力 // nakladak -> truck, zatrub -> honk, Také vypíše 'Bip bip!' -> 同様に 'Bip bip!' を出力

この例では、1つのメソッドhonk()を含むHonkingという名前のトレイトがあります。次に、CarTruckの2つのクラスがあり、どちらもHonkingトレイトを使用しています。これにより、両方のクラスがhonk()メソッドを「持ち」、両方のクラスのオブジェクトでそれを呼び出すことができます。

トレイトを使用すると、クラス間でコードを簡単かつ効率的に共有できます。同時に、それらは継承階層には入りません。つまり、$car instanceof Honkingfalseを返します。

例外

OOPの例外を使用すると、コード内のエラーや予期しない状況をエレガントに処理できます。これらは、エラーまたは異常な状況に関する情報を持つオブジェクトです。

PHPには、すべての例外の基礎として機能する組み込みクラスExceptionがあります。これには、エラーメッセージ、エラーが発生したファイルと行など、例外に関する詳細情報を取得できるいくつかのメソッドがあります。

コードでエラーが発生した場合、キーワードthrowを使用して例外を「スロー」できます。

function divide(float $a, float $b): float // deleni -> divide
{
	if ($b === 0) {
		throw new Exception('ゼロ除算!'); // Dělení nulou! -> ゼロ除算!
	}
	return $a / $b;
}

関数divide()が2番目の引数としてゼロを受け取ると、エラーメッセージ'ゼロ除算!'を持つ例外をスローします。例外がスローされたときにプログラムがクラッシュするのを防ぐために、try/catchブロックでそれをキャッチします:

try {
	echo divide(10, 0); // deleni -> divide
} catch (Exception $e) {
	echo '例外がキャッチされました: '. $e->getMessage(); // Výjimka zachycena: -> 例外がキャッチされました:
}

例外をスローする可能性のあるコードは、tryブロックにラップされます。例外がスローされると、コードの実行はcatchブロックに移動し、そこで例外を処理できます(例:エラーメッセージを表示)。

tryおよびcatchブロックの後には、オプションのfinallyブロックを追加できます。これは、例外がスローされたかどうかに関係なく常に実行されます(tryまたはcatchブロックでreturnbreak、またはcontinueステートメントを使用した場合でも):

try {
	echo divide(10, 0); // deleni -> divide
} catch (Exception $e) {
	echo '例外がキャッチされました: '. $e->getMessage(); // Výjimka zachycena: -> 例外がキャッチされました:
} finally {
	// 例外がスローされたかどうかに関係なく、常に実行されるコード // Kód, který se provede vždy, ať už byla výjimka vyhozena nebo ne -> 例外がスローされたかどうかに関係なく、常に実行されるコード
}

Exceptionクラスから継承する独自の例外クラス(階層)を作成することもできます。例として、入金と引き出しを実行できる簡単な銀行アプリケーションを想像してみましょう:

class BankException extends Exception {} // BankovniVyjimka -> BankException
class InsufficientFundsException extends BankException {} // NedostatekProstredkuVyjimka -> InsufficientFundsException, BankovniVyjimka -> BankException
class LimitExceededException extends BankException {} // PrekroceniLimituVyjimka -> LimitExceededException, BankovniVyjimka -> BankException

class BankAccount // BankovniUcet -> BankAccount
{
	private int $balance = 0; // zustatek -> balance
	private int $dailyLimit = 1000; // denniLimit -> dailyLimit

	public function deposit(int $amount): int // vlozit -> deposit, castka -> amount
	{
		$this->balance += $amount; // zustatek -> balance, castka -> amount
		return $this->balance; // zustatek -> balance
	}

	public function withdraw(int $amount): int // vybrat -> withdraw, castka -> amount
	{
		if ($amount > $this->balance) { // castka -> amount, zustatek -> balance
			throw new InsufficientFundsException('口座に十分な残高がありません。'); // NedostatekProstredkuVyjimka -> InsufficientFundsException, Na účtu není dostatek prostředků. -> 口座に十分な残高がありません。
		}

		if ($amount > $this->dailyLimit) { // castka -> amount, denniLimit -> dailyLimit
			throw new LimitExceededException('1日の引き出し限度額を超えました。'); // PrekroceniLimituVyjimka -> LimitExceededException, Byl překročen denní limit pro výběry. -> 1日の引き出し限度額を超えました。
		}

		$this->balance -= $amount; // zustatek -> balance, castka -> amount
		return $this->balance; // zustatek -> balance
	}
}

1つのtryブロックに対して、異なるタイプの例外を予期する場合は、複数のcatchブロックを指定できます。

$account = new BankAccount; // ucet -> account, BankovniUcet -> BankAccount
$account->deposit(500); // ucet -> account, vlozit -> deposit

try {
	$account->withdraw(1500); // ucet -> account, vybrat -> withdraw
} catch (LimitExceededException $e) { // PrekroceniLimituVyjimka -> LimitExceededException
	echo $e->getMessage();
} catch (InsufficientFundsException $e) { // NedostatekProstredkuVyjimka -> InsufficientFundsException
	echo $e->getMessage();
} catch (BankException $e) { // BankovniVyjimka -> BankException
	echo '操作の実行中にエラーが発生しました。'; // Vyskytla se chyba při provádění operace. -> 操作の実行中にエラーが発生しました。
}

この例では、catchブロックの順序に注意することが重要です。すべての例外はBankExceptionから継承するため、このブロックを最初に配置した場合、後続のcatchブロックにコードが到達することなく、すべての例外がここでキャッチされてしまいます。したがって、より具体的な例外(つまり、他の例外から継承するもの)を、親の例外よりもcatchブロックの順序で上に配置することが重要です。

イテレーション

PHPでは、配列を反復処理するのと同様に、foreachループを使用してオブジェクトを反復処理できます。これが機能するためには、オブジェクトは特別なインターフェースを実装する必要があります。

最初のオプションは、Iteratorインターフェースを実装することです。これには、現在の値を返すcurrent()、キーを返すkey()、次の値に移動するnext()、先頭に移動するrewind()、そしてまだ終端に達していないかどうかを確認するvalid()メソッドがあります。

2番目のオプションは、IteratorAggregateインターフェースを実装することです。これにはgetIterator()という1つのメソッドしかありません。これは、反復処理を保証するプレースホルダーオブジェクトを返すか、またはジェネレータを表すことができます。ジェネレータは、キーと値を段階的に返すためにyieldを使用する特別な関数です:

class Person // Osoba -> Person
{
	public function __construct(
		public int $age, // vek -> age
	) {
	}
}

class List implements IteratorAggregate // Seznam -> List
{
	private array $people = []; // osoby -> people

	public function addPerson(Person $person): void // pridatOsobu -> addPerson, osoba -> person, Osoba -> Person
	{
		$this->people[] = $person; // osoby -> people, osoba -> person
	}

	public function getIterator(): Generator
	{
		foreach ($this->people as $person) { // osoby -> people, osoba -> person
			yield $person; // osoba -> person
		}
	}
}

$list = new List; // seznam -> list, Seznam -> List
$list->addPerson(new Person(30)); // seznam -> list, pridatOsobu -> addPerson, Osoba -> Person
$list->addPerson(new Person(25)); // seznam -> list, pridatOsobu -> addPerson, Osoba -> Person

foreach ($list as $person) { // seznam -> list, osoba -> person
	echo "年齢: {$person->age} 歳 \n"; // Věk: {$osoba->vek} let \n -> 年齢: {$person->age} 歳 \n
}

ベストプラクティス

オブジェクト指向プログラミングの基本原則を理解したら、OOPのベストプラクティスに焦点を当てることが重要です。これらは、機能的であるだけでなく、読みやすく、理解しやすく、保守しやすいコードを書くのに役立ちます。

  1. 関心の分離 (Separation of Concerns): 各クラスは明確に定義された責任を持ち、1つの主要なタスクのみを解決する必要があります。クラスが多くのことを行いすぎる場合は、より小さく、特化したクラスに分割するのが適切かもしれません。
  2. カプセル化 (Encapsulation): データとメソッドは可能な限り隠蔽し、定義されたインターフェースを通じてのみアクセス可能であるべきです。これにより、コードの残りの部分に影響を与えることなく、クラスの内部実装を変更できます。
  3. 依存性の注入 (Dependency Injection): クラス内で直接依存関係を作成する代わりに、外部から「注入」する必要があります。この原則をより深く理解するために、Dependency Injectionに関する章をお勧めします。
バージョン: 4.0