PHPオブジェクト指向の第一歩:これだけは知っておきたい基礎

はい、承知いたしました。PHPオブジェクト指向プログラミングの基礎を網羅した、約5000語の詳細な記事を作成します。


PHPオブジェクト指向の第一歩:これだけは知っておきたい基礎

はじめに:なぜ今、オブジェクト指向を学ぶべきなのか?

PHPを学び始めた皆さん、あるいはすでにPHPでウェブサイトやアプリケーションを開発している皆さんは、おそらく手続き型プログラミングに慣れているかもしれません。上から順番にコードが実行され、関数を呼び出し、処理を進めていくスタイルです。これは小規模なスクリプトや簡単なタスクには非常に効果的でわかりやすいアプローチです。

しかし、開発するアプリケーションが大規模になり、機能が複雑になるにつれて、手続き型プログラミングには限界が見えてきます。コードが肥大化し、どこで何が行われているのか把握しにくくなったり、機能を追加・修正する際に予期せぬ副作用が発生したりすることが増えてくるのです。このような問題を解決し、より効率的で保守性の高いコードを書くための強力なパラダイムが「オブジェクト指向プログラミング(Object-Oriented Programming, OOP)」です。

オブジェクト指向は、現実世界の考え方をプログラミングに取り入れたものです。現実世界には、さまざまな「モノ」(オブジェクト)が存在します。例えば、「自動車」というモノは、「色」「モデル」「走行距離」といった「状態(属性)」と、「走る」「止まる」「曲がる」といった「振る舞い(操作)」を持っています。オブジェクト指向では、このような「モノ」をプログラム上の「オブジェクト」として表現し、そのオブジェクトが持つべき状態と振る舞いを定義して扱います。

OOPを導入することで、以下のようなメリットが得られます。

  1. 保守性の向上: 機能ごとにコードが構造化されるため、問題発生時の原因究明や修正が容易になります。
  2. 再利用性の向上: 一度作ったオブジェクトやクラスは、他の場所や別のプロジェクトでも再利用できます。
  3. 開発効率の向上: 機能ごとの担当を分けやすく、並行開発がしやすくなります。また、再利用により全体のコード量を削減できます。
  4. 拡張性の向上: 新しい機能を追加する際に、既存のコードへの影響を最小限に抑えながら実装できます。
  5. 品質の向上: コードがモジュール化されることで、テストがしやすくなり、バグの混入を防ぐことができます。

もちろん、オブジェクト指向は万能薬ではありません。導入には学習コストがかかりますし、不適切に使えばかえってコードを複雑にしてしまうこともあります。しかし、特に現代のPHP開発、特にLaravelやSymfonyといったフレームワークを使った開発においては、オブジェクト指向の理解は必須と言えます。

この記事では、PHPにおけるオブジェクト指向プログラミングの「第一歩」として、これだけは知っておきたい基礎中の基礎を、具体的なコード例を交えながら徹底的に解説します。クラス、オブジェクト、プロパティ、メソッドといった基本的な要素から、カプセル化、継承、抽象化、ポリモーフィズムといったOOPの主要な原則まで、一つずつ丁寧に見ていきましょう。この記事を最後まで読めば、PHPでオブジェクト指向のコードを読み書きするための確かな基礎が身につくはずです。さあ、オブジェクト指向の世界への扉を開きましょう!

OOPの基本概念を理解する

オブジェクト指向プログラミングの中心となるのは、「クラス」と「オブジェクト」という概念です。これらがどのように関連しているのか、そしてクラスを構成する「プロパティ」と「メソッド」について見ていきましょう。

クラス(Class):設計図

オブジェクト指向における「クラス」は、オブジェクトの「設計図」や「ひな形」のようなものです。クラスには、そのクラスから作られるオブジェクトが共通して持つべき「状態」(どのようなデータを持つか)と「振る舞い」(どのような操作ができるか)を定義します。

例えば、「自動車」というクラスを考える場合、その設計図には以下のような情報が含まれるでしょう。

  • 状態(プロパティ): 色、モデル名、年式、走行距離など
  • 振る舞い(メソッド): エンジンをかける、走る、止まる、曲がる、給油するなど

PHPでクラスを定義するには、class キーワードを使います。

“`php

“`

このコードは、「Car」という名前のクラスを定義しただけです。まだ「自動車」という実体は生まれていません。あくまで「自動車とはこういうものです」という設計図を描いた状態です。

オブジェクト(Object / Instance):実体

クラスという設計図から実際に作られた「モノ」が「オブジェクト」、または「インスタンス」と呼ばれます。一つのクラスから、異なる状態を持つ複数のオブジェクトを作ることができます。

例えば、「自動車」の設計図(Carクラス)から、赤いセダン、青いSUV、黒い軽自動車といった具体的なオブジェクト(インスタンス)を作り出すことができます。

  • オブジェクトA: 色: 赤, モデル: セダン, 年式: 2020, 走行距離: 10000km
  • オブジェクトB: 色: 青, モデル: SUV, 年式: 2022, 走行距離: 5000km
  • オブジェクトC: 色: 黒, モデル: 軽自動車, 年式: 2019, 走行距離: 30000km

PHPでクラスからオブジェクトを生成するには、new キーワードを使います。

“`php

“`

new Car() という記述によって、メモリ上にCarクラスに基づいた領域が確保され、オブジェクトが生成されます。そして、そのオブジェクトへの参照が変数 $car1$car2 に代入されます。これで、それぞれのオブジェクトが持つプロパティに値を設定したり、メソッドを呼び出したりできるようになります。

プロパティ(Property):オブジェクトの状態

プロパティは、オブジェクトが持つ「状態」や「属性」を表す変数です。クラス定義の中で public, protected, private といったアクセス修飾子(これについては後述します)とともに宣言します。

オブジェクトが生成された後、そのプロパティにアクセスするには、オブジェクトの変数名の後に -> 演算子を使い、その後にプロパティ名を記述します。

“`php

color = ‘赤’;
$car1->model = ‘セダン’;
$car1->year = 2020;
// $mileage は初期値 0 が設定されていますが、必要なら変更も可能
// $car1->mileage = 10000;

// $car2 オブジェクトを生成し、別の値を設定
$car2 = new Car();
$car2->color = ‘青’;
$car2->model = ‘SUV’;
$car2->year = 2022;
$car2->mileage = 5000;

// 各オブジェクトのプロパティの値を取得して表示
echo “Car 1:\n”;
echo ” 色: ” . $car1->color . “\n”;
echo ” モデル: ” . $car1->model . “\n”;
echo ” 年式: ” . $car1->year . “\n”;
echo ” 走行距離: ” . $car1->mileage . “km\n”;

echo “\nCar 2:\n”;
echo ” 色: ” . $car2->color . “\n”;
echo ” モデル: ” . $car2->model . “\n”;
echo ” 年式: ” . $car2->year . “\n”;
echo ” 走行距離: ” . $car2->mileage . “km\n”;

?>

“`

実行結果:

“`
Car 1:
色: 赤
モデル: セダン
年式: 2020
走行距離: 0km

Car 2:
色: 青
モデル: SUV
年式: 2022
走行距離: 5000km
“`

このように、それぞれのオブジェクトが独立したプロパティの値を保持していることがわかります。

メソッド(Method):オブジェクトの振る舞い

メソッドは、オブジェクトが実行できる「振る舞い」や「操作」を表す関数です。クラス定義の中で public, protected, private といったアクセス修飾子とともに、通常の関数と同じように定義します。

メソッドは、そのオブジェクトが持つプロパティの値を利用したり、プロパティの値を変更したり、他の操作を実行したりすることができます。メソッド内で、自分自身のオブジェクトを参照するには $this キーワードを使います。$this は、メソッドが呼び出されたオブジェクト自身を指します。

“`php

model . ” のエンジンがかかりました。\n”;
}

// 指定された距離を走行するメソッド
public function drive(int $distance)
{
if ($distance > 0) {
$this->mileage += $distance; // 走行距離を更新
echo $this->model . ” が ” . $distance . “km 走行しました。\n”;
echo “現在の走行距離は ” . $this->mileage . “km です。\n”;
} else {
echo “走行距離は正の値で指定してください。\n”;
}
}

// 現在の車の情報を表示するメソッド
public function displayInfo()
{
echo “—- 車両情報 —-\n”;
echo ” モデル: ” . $this->model . “\n”;
echo ” 色: ” . $this->color . “\n”;
echo ” 年式: ” . $this->year . “\n”;
echo ” 走行距離: ” . $this->mileage . “km\n”;
echo “————–\n”;
}
}

$car1 = new Car();
$car1->model = ‘セダン’;
$car1->color = ‘赤’;
$car1->year = 2020;

$car2 = new Car();
$car2->model = ‘SUV’;
$car2->color = ‘青’;
$car2->year = 2022;
$car2->mileage = 5000; // 事前に走行していたと仮定

// オブジェクトのメソッドを呼び出し
$car1->engineStart();
$car1->drive(100);
$car1->drive(50);
$car1->displayInfo();

echo “\n”;

$car2->engineStart();
$car2->drive(200);
$car2->displayInfo();

?>

“`

実行結果:

“`
セダンのエンジンがかかりました。
セダンが 100km 走行しました。
現在の走行距離は 100km です。
セダンが 50km 走行しました。
現在の走行距離は 150km です。
—- 車両情報 —-
モデル: セダン
色: 赤
年式: 2020
走行距離: 150km


SUVのエンジンがかかりました。
SUVが 200km 走行しました。
現在の走行距離は 5200km です。
—- 車両情報 —-
モデル: SUV
色: 青
年式: 2022
走行距離: 5200km


“`

メソッド内で $this->mileage とすることで、メソッドが呼び出された $car1 オブジェクト(または $car2 オブジェクト)自身の mileage プロパティにアクセスしていることがわかります。メソッドを呼び出すことで、オブジェクトの状態(プロパティの値)が変化したり、特定の処理が実行されたりします。

コンストラクタ(Constructor):オブジェクト生成時の初期化

オブジェクトが生成される際に、特別な初期化処理を行いたい場合があります。例えば、プロパティに初期値を設定したり、オブジェクトが依存する他のオブジェクトを設定したりするなどです。このような目的のために使用されるのが「コンストラクタ」です。

コンストラクタは、オブジェクトが new キーワードで生成された直後に自動的に実行される特殊なメソッドです。PHPでは、コンストラクタは __construct() という名前で定義します。メソッド名がアンダースコア2つで始まるものは、PHPが特別に扱う「マジックメソッド」と呼ばれます。

“`php

model = $model;
$this->color = $color;
$this->year = $year;
// mileage は初期値 0 があるのでここでは設定不要だが、
// 必要であれば $this->mileage = 0; などと設定することもできる。

echo “新しい {$this->model} オブジェクトが生成されました。\n”;
}

public function displayInfo()
{
echo “—- 車両情報 —-\n”;
echo ” モデル: ” . $this->model . “\n”;
echo ” 色: ” . $this->color . “\n”;
echo ” 年式: ” . $this->year . “\n”;
echo ” 走行距離: ” . $this->mileage . “km\n”;
echo “————–\n”;
}

// drive メソッドなどは省略
}

// new キーワードでオブジェクトを生成する際に、コンストラクタの引数を渡す
$car1 = new Car(‘セダン’, ‘赤’, 2020);
$car2 = new Car(‘SUV’, ‘青’, 2022);
// mileage は初期値 0 で生成される

$car1->displayInfo();
$car2->displayInfo();

?>

“`

実行結果:

“`
新しい セダン オブジェクトが生成されました。
新しい SUV オブジェクトが生成されました。
—- 車両情報 —-
モデル: セダン
色: 赤
年式: 2020
走行距離: 0km


—- 車両情報 —-
モデル: SUV
色: 青
年式: 2022
走行距離: 0km


“`

コンストラクタを定義することで、オブジェクトを生成する際に必ず必要な情報を引数として渡すように強制したり、プロパティに適切な初期値を設定したりすることができます。これにより、不完全な状態のオブジェクトが生成されるのを防ぐことができます。

アクセス修飾子(Visibility)とカプセル化(Encapsulation)

オブジェクト指向の重要な原則の一つに「カプセル化」があります。これは、オブジェクトの内部の状態(プロパティ)や詳細な実装(メソッド)を外部から直接アクセスできないように隠蔽し、決められた方法(通常は公開されたメソッド)を介してのみやり取りできるようにすることです。

なぜカプセル化が必要なのでしょうか?

  • 内部実装の変更への耐性: オブジェクトの内部構造やアルゴリズムを変更しても、外部からの呼び出し方法(公開されたメソッドのインターフェース)が変わらなければ、そのオブジェクトを利用している他のコードに影響を与えずに済みます。
  • データの整合性の維持: プロパティへの不正な値の設定を防ぐことができます。例えば、人の年齢がマイナスになったり、車の走行距離が突然減少したりするのを防ぐために、プロパティに値を設定する際にはバリデーション(値の検証)を行うメソッドを経由させることができます。
  • コードの利用方法を明確にする: 外部からアクセスできる部分とできない部分を明確にすることで、そのオブジェクトをどのように使えば良いかをわかりやすくします。

PHPでは、カプセル化を実現するために「アクセス修飾子(Visibility)」を使用します。プロパティやメソッドの定義の前に以下のいずれかのキーワードをつけます。

  • public: どこからでも(クラス自身、継承したクラス、外部のコード)アクセス可能です。これがデフォルトのアクセスレベルです。
  • protected: クラス自身と、そのクラスを継承したクラスからのみアクセス可能です。
  • private: クラス自身からのみアクセス可能です。継承したクラスや外部のコードからはアクセスできません。

先ほどの Car クラスの例では、すべてのプロパティとメソッドを public にしていました。これはカプセル化の観点からはあまり望ましくありません。例えば、mileage プロパティを外部から直接負の値に書き換えられてしまうと、データの整合性が崩れます。

php
$car1 = new Car('セダン', '赤', 2020);
$car1->mileage = -100; // public なら可能だが、これはおかしい!

カプセル化を適用して、mileage プロパティを private にしてみましょう。そして、走行距離を増やすための drive メソッドを介してのみ変更できるようにします。現在の走行距離を取得するためのメソッドも追加します(これを「ゲッター」と呼びます)。

“`php

model = $model;
$this->color = $color;
$this->year = $year;
}

// プロパティの値を設定するメソッド(セッター)
// color プロパティ用のセッターの例(ここでは単純に代入)
// public function setColor(string $color)
// {
// $this->color = $color;
// }

// プロパティの値を取得するメソッド(ゲッター)
public function getModel(): string
{
return $this->model;
}

public function getColor(): string
{
return $this->color;
}

public function getYear(): int
{
return $this->year;
}

public function getMileage(): int
{
return $this->mileage;
}

// 走行距離を変更するメソッド(カプセル化の恩恵を受ける例)
public function drive(int $distance)
{
// ここでバリデーションを行うことができる
if ($distance > 0) {
$this->mileage += $distance; // private プロパティへのアクセス
echo $this->getModel() . ” が ” . $distance . “km 走行しました。\n”;
} else {
echo $this->getModel() . “:走行距離は正の値で指定してください。\n”;
}
}

public function displayInfo()
{
// クラス内部からは private プロパティにアクセスできる
echo “—- 車両情報 —-\n”;
echo ” モデル: ” . $this->model . “\n”;
echo ” 色: ” . $this->color . “\n”;
echo ” 年式: ” . $this->year . “\n”;
echo ” 走行距離: ” . $this->mileage . “km\n”;
echo “————–\n”;
}

// engineStart メソッドは public のままとする
public function engineStart()
{
echo $this->getModel() . ” のエンジンがかかりました。\n”;
}
}

$car1 = new Car(‘セダン’, ‘赤’, 2020);

// private プロパティには外部から直接アクセスできない(エラーになる)
// $car1->mileage = 100; // Fatal error: Uncaught Error: Cannot access private property Car::$mileage

// プロパティの値を取得するにはゲッターを使う
echo “モデル名: ” . $car1->getModel() . “\n”;
echo “現在の走行距離: ” . $car1->getMileage() . “km\n”;

// 走行距離を増やすには drive メソッドを使う
$car1->drive(100);
$car1->drive(50);

// 不正な値を設定しようとしても、メソッド内で弾かれる
$car1->drive(-10); // バリデーションにより拒否される

echo “更新後の走行距離: ” . $car1->getMileage() . “km\n”;

$car1->displayInfo(); // displayInfo メソッドは内部から private プロパティにアクセス可能

?>

“`

実行結果:

“`
モデル名: セダン
現在の走行距離: 0km
セダン が 100km 走行しました。
セダン が 50km 走行しました。
セダン:走行距離は正の値で指定してください。
更新後の走行距離: 150km
—- 車両情報 —-
モデル: セダン
色: 赤
年式: 2020
走行距離: 150km


“`

この例のように、プロパティを private にすることで、外部からの直接的な変更を防ぎ、データの整合性を守ることができます。プロパティの値を取得・設定したい場合は、それぞれに対応する public メソッド(ゲッター、セッター)を用意します。メソッド内で必要なロジック(バリデーションなど)を記述することで、オブジェクトの状態を安全に管理できます。

protected 修飾子については、次の「継承」のセクションでその使いどころを見ていきます。

継承(Inheritance)

オブジェクト指向のもう一つの重要な原則は「継承」です。これは、既存のクラス(親クラス、スーパークラス、基底クラス)が持つプロパティとメソッドを、新しいクラス(子クラス、サブクラス、派生クラス)が引き継ぐ仕組みです。

継承のメリットは、コードの再利用と階層構造による整理です。共通する機能は親クラスにまとめ、子クラスでは親クラスの機能をそのまま使うか、あるいは子クラス固有の機能を追加したり、親クラスの機能を自分向けに変更したり(オーバーライド)します。

例えば、「自動車 (Car)」クラスを親として、「乗用車 (PassengerCar)」クラスや「トラック (Truck)」クラスを子クラスとして定義することを考えてみましょう。乗用車もトラックも「自動車」の一種ですから、「色」「モデル」「年式」「走行距離」といったプロパティや、「エンジンをかける」「走る」「止まる」といったメソッドは共通して持つはずです。これらの共通部分は Car クラスに定義し、PassengerCar クラスや Truck クラスはそれを引き継ぎます。さらに、PassengerCar は「乗車定員」、Truck は「最大積載量」といった固有のプロパティを持つかもしれませんし、それぞれに特有のメソッドを持つかもしれません。

PHPでクラスを継承するには、子クラスの定義時に extends キーワードと親クラスの名前を指定します。

“`php

model = $model;
$this->color = $color;
$this->year = $year;
echo “親クラス Car のコンストラクタが呼ばれました: {$this->model}\n”;
}

public function getModel(): string { return $this->model; }
public function getColor(): string { return $this->color; }
public function getYear(): int { return $this->year; }
public function getMileage(): int { return $this->mileage; } // mileage は private なので Car クラスにゲッターが必要

public function drive(int $distance)
{
if ($distance > 0) {
$this->mileage += $distance;
echo $this->getModel() . ” が ” . $distance . “km 走行しました (Carクラス).\n”;
} else {
echo $this->getModel() . “:走行距離は正の値で指定してください (Carクラス).\n”;
}
}

// displayInfo メソッドも protected に変更して、子クラスでのオーバーライドを想定
protected function displayBasicInfo()
{
echo ” モデル: ” . $this->model . “\n”;
echo ” 色: ” . $this->color . “\n”;
echo ” 年式: ” . $this->year . “\n”;
echo ” 走行距離: ” . $this->mileage . “km\n”;
}
}

// Car クラスを継承して PassengerCar クラスを定義
class PassengerCar extends Car
{
private $passengerCapacity; // 乗用車固有のプロパティ

// 子クラスのコンストラクタ
// 親クラスのコンストラクタを呼び出す必要がある場合は、parent::__construct() を使う
public function __construct(string $model, string $color, int $year, int $capacity)
{
// まず親クラスのコンストラクタを呼び出し、親に必要な初期化を行わせる
parent::__construct($model, $color, $year);

// 子クラス固有のプロパティを初期化
$this->passengerCapacity = $capacity;
echo “子クラス PassengerCar のコンストラクタが呼ばれました。\n”;
}

// 乗用車固有のメソッド
public function getPassengerCapacity(): int
{
return $this->passengerCapacity;
}

// 親クラスのメソッドをオーバーライド(再定義)
// public function drive(int $distance)
// {
// // 親の drive メソッドの処理に加えて、何か別の処理を追加するなど
// parent::drive($distance); // 必要であれば親のメソッドを呼び出す
// echo $this->getModel() . ” は快適に走行しました。\n”; // 子クラス固有の処理
// }

// 親クラスのメソッドをオーバーライドして、子クラス独自の表示を追加
public function displayInfo()
{
echo “—- 乗用車情報 —-\n”;
$this->displayBasicInfo(); // protected メソッドは子クラスからアクセス可能
echo ” 乗車定員: ” . $this->passengerCapacity . “名\n”;
echo “————–\n”;
}
}

// Car クラスを継承して Truck クラスを定義
class Truck extends Car
{
private $maxLoadCapacity; // トラック固有のプロパティ

public function __construct(string $model, string $color, int $year, int $loadCapacity)
{
parent::__construct($model, $color, $year);
$this->maxLoadCapacity = $loadCapacity;
echo “子クラス Truck のコンストラクタが呼ばれました。\n”;
}

public function getMaxLoadCapacity(): int
{
return $this->maxLoadCapacity;
}

// 親クラスの drive メソッドをオーバーライド
public function drive(int $distance)
{
if ($distance > 0) {
// protected プロパティは子クラスからアクセス可能
$this->mileage += $distance; // あれ? mileage は private だった!
// そう、private プロパティ/メソッドは子クラスからもアクセスできません。
// このため、 mileage は Car クラスの drive メソッド内で更新するか、
// mileage を protected に変更する必要があります。
// ここでは、Car クラスの drive メソッドを呼び出すことにします。
parent::drive($distance);
echo $this->getModel() . ” は重い荷物を積んで走行しました (Truckクラス).\n”; // 子クラス固有の処理
} else {
parent::drive($distance); // 親のバリデーションメッセージを利用
}
}

public function displayInfo()
{
echo “—- トラック情報 —-\n”;
$this->displayBasicInfo(); // protected メソッドは子クラスからアクセス可能
echo ” 最大積載量: ” . $this->maxLoadCapacity . “kg\n”;
echo “————–\n”;
}
}

echo “— PassengerCar オブジェクトの生成 —\n”;
$myCar = new PassengerCar(‘プリウス’, ‘白’, 2021, 5);

echo “\n— Truck オブジェクトの生成 —\n”;
$myTruck = new Truck(‘ふそう’, ‘緑’, 2018, 10000);

echo “\n— メソッドの呼び出し —\n”;
$myCar->drive(200); // PassengerCar は drive メソッドをオーバーライドしていないので、Carクラスの drive が実行される
$myTruck->drive(50); // Truck は drive メソッドをオーバーライドしているので、Truckクラスの drive が実行される(その中で親の drive も呼んでいる)

echo “\n— 情報表示 —\n”;
$myCar->displayInfo(); // PassengerCar の displayInfo が実行される
$myTruck->displayInfo(); // Truck の displayInfo が実行される

// 子クラスのインスタンスは親クラスの型としても扱える(後述の多態性につながる)
// $vehicle: Car オブジェクトかもしれないし、PassengerCar オブジェクトかもしれないし、Truck オブジェクトかもしれない
function showVehicleInfo(Car $vehicle) {
// Car クラスに定義されているメソッドは呼び出せる
// $vehicle->displayInfo(); // これはダメ! Car クラスに displayInfo は protected としてしか定義していないため。
// public なメソッドまたは protected だが $vehicle が Car の子クラスなら呼び出し可。
// protected な displayBasicInfo は呼び出せない(showVehicleInfo 関数は Car またはその子クラスの内部ではない)
echo “モデル(親クラスのゲッター経由): ” . $vehicle->getModel() . “\n”;
// echo “乗車定員(子クラス固有のメソッド): ” . $vehicle->getPassengerCapacity() . “\n”; // これはエラー! $vehicle が常に PassengerCar とは限らないため。
}

echo “\n— 親クラスの型として扱う例 —\n”;
showVehicleInfo($myCar);
showVehicleInfo($myTruck);

?>

“`

実行結果:

“`
— PassengerCar オブジェクトの生成 —
親クラス Car のコンストラクタが呼ばれました: プリウス
子クラス PassengerCar のコンストラクタが呼ばれました。

— Truck オブジェクトの生成 —
親クラス Car のコンストラクタが呼ばれました: ふそう
子クラス Truck のコンストラクタが呼ばれました。

— メソッドの呼び出し —
プリウス が 200km 走行しました (Carクラス).
ふそう が 50km 走行しました (Carクラス).
ふそう は重い荷物を積んで走行しました (Truckクラス).

— 情報表示 —
—- 乗用車情報 —-
モデル: プリウス
色: 白
年式: 2021
走行距離: 200km
乗車定員: 5名


—- トラック情報 —-
モデル: ふそう
色: 緑
年式: 2018
走行距離: 50km
最大積載量: 10000kg


— 親クラスの型として扱う例 —
モデル(親クラスのゲッター経由): プリウス
モデル(親クラスのゲッター経由): ふそう
“`

この例から、以下のことがわかります。

  • 子クラスは extends で親クラスを指定して定義します。
  • 親クラスの public および protected なプロパティとメソッドは子クラスに継承されます。private なものは継承されません。
  • 子クラスで親クラスと同じ名前のメソッドを定義すると、それは「オーバーライド」となり、子クラスのオブジェクトでそのメソッドを呼び出したときは子クラスのメソッドが実行されます。
  • 子クラスのコンストラクタでは、通常 parent::__construct() を呼び出して親クラスのコンストラクタを実行します。これを忘れると、親クラスに必要な初期化が行われない可能性があります。
  • メソッド内で parent:: を使うと、オーバーライドされていても親クラスのメソッドを呼び出すことができます。
  • 子クラスのインスタンス(例: $myCar, $myTruck)は、親クラスの型(Car)として扱うことができます。これは後述する「多態性」の重要な要素です。

継承はコードの共通化と再利用に役立ちますが、あまり深く階層構造を作りすぎたり、適切でない継承関係を結んでしまうと、かえってコードが複雑になり、保守が難しくなることもあります(「継承よりコンポジション」という考え方もありますが、これは基礎の次のステップで学ぶことが多いでしょう)。基礎としては、IS-A関係(「〜は〜の一種である」)が成り立つ場合に継承を用いる、と理解しておくと良いでしょう。(例: PassengerCar IS A Car, Truck IS A Car)

抽象化(Abstraction)

オブジェクト指向のもう一つの重要な原則が「抽象化」です。抽象化とは、複雑な物事から本質的な要素だけを取り出し、それ以外の詳細を隠すことです。プログラミングにおいては、オブジェクトが持つべき共通の振る舞い(メソッド)の「契約」を定義し、その具体的な実装はサブクラスに任せる、といった形で実現されます。

PHPで抽象化を実現する主な手段として、「抽象クラス (Abstract Class)」と「インターフェース (Interface)」があります。

抽象クラス(Abstract Class)

抽象クラスは、それ自体はインスタンス化(new できない)できないクラスです。主に、他のクラスに継承されることを目的として定義されます。抽象クラスは、通常のクラスと同様にプロパティや具象メソッド(実装を持つメソッド)を持つことができますが、それに加えて「抽象メソッド (Abstract Method)」を持つことができます。

抽象メソッドは、メソッドのシグネチャ(メソッド名、引数、返り値の型宣言など)だけを定義し、具体的な処理内容(実装)は持ちません。抽象メソッドを含む抽象クラスを継承する子クラスは、必ずその抽象メソッドを実装しなければなりません。これにより、特定の振る舞いを子クラスに実装させることを強制できます。

抽象クラスは abstract class キーワードで定義し、抽象メソッドはメソッド定義の前に abstract キーワードをつけて定義します。

“`php

model = $model;
$this->year = $year;
echo “抽象クラス Vehicle のコンストラクタが呼ばれました: {$this->model}\n”;
}

public function getModel(): string { return $this->model; }
public function getYear(): int { return $this->year; }
public function getMileage(): int { return $this->mileage; }

// 具体的なメソッドも持つことができる
public function increaseMileage(int $distance)
{
if ($distance > 0) {
$this->mileage += $distance;
echo $this->getModel() . ” の走行距離が ” . $distance . “km 増えました (Vehicleクラス).\n”;
}
}

// 抽象メソッド
// このメソッドは実装を持たない({}がない)
// このクラスを継承する具象クラスは、必ずこのメソッドを実装しなければならない
abstract public function getDescription(): string;

// 別の抽象メソッド
abstract public function startEngine(): void;
}

// 抽象クラス Vehicle を継承する具象クラス Car
class Car extends Vehicle
{
private $color;

public function __construct(string $model, int $year, string $color)
{
parent::__construct($model, $year);
$this->color = $color;
echo “具象クラス Car のコンストラクタが呼ばれました。\n”;
}

public function getColor(): string
{
return $this->color;
}

// 親クラスの抽象メソッド getDescription を実装する
public function getDescription(): string
{
return “これは {$this->getYear()}年製 の {$this->color} の {$this->getModel()} です。”;
}

// 親クラスの抽象メソッド startEngine を実装する
public function startEngine(): void
{
echo $this->getModel() . ” のエンジンがキュルキュル…ブォン!とかかりました。\n”;
}

// Car 固有のメソッド
public function honk(): void
{
echo “Beep beep!\n”;
}
}

// 抽象クラス Vehicle を継承する具象クラス Bicycle
class Bicycle extends Vehicle
{
private $type; // 例: マウンテンバイク, ロードバイク

public function __construct(string $model, int $year, string $type)
{
parent::__construct($model, $year);
$this->type = $type;
echo “具象クラス Bicycle のコンストラクタが呼ばれました。\n”;
}

public function getType(): string
{
return $this->type;
}

// 親クラスの抽象メソッド getDescription を実装する
public function getDescription(): string
{
return “これは {$this->getYear()}年製 の {$this->type} である {$this->getModel()} です。”;
}

// 親クラスの抽象メソッド startEngine を実装する
// 自転車にはエンジンがないので、ここでは何もしないか、あるいは別のメッセージを表示する
public function startEngine(): void
{
echo $this->getModel() . ” にエンジンはありません。\n”;
}

// Bicycle 固有のメソッド
public function ringBell(): void
{
echo “Ching ching!\n”;
}
}

// 抽象クラスはインスタンス化できない
// $vehicle = new Vehicle(‘汎用乗り物’, 2023); // Fatal error: Cannot instantiate abstract class Vehicle

// 具象クラスはインスタンス化できる
echo “— Car オブジェクトの生成 —\n”;
$myCar = new Car(‘プリウス’, 2021, ‘白’);

echo “\n— Bicycle オブジェクトの生成 —\n”;
$myBicycle = new Bicycle(‘GIANT TCR’, 2022, ‘ロードバイク’);

echo “\n— メソッドの呼び出し —\n”;
echo $myCar->getDescription() . “\n”;
$myCar->startEngine();
$myCar->honk();
$myCar->increaseMileage(100); // 親クラスの具象メソッドを呼び出し

echo “\n”;

echo $myBicycle->getDescription() . “\n”;
$myBicycle->startEngine();
$myBicycle->ringBell();
$myBicycle->increaseMileage(10); // 親クラスの具象メソッドを呼び出し

?>

“`

実行結果:

“`
— Car オブジェクトの生成 —
抽象クラス Vehicle のコンストラクタが呼ばれました: プリウス
具象クラス Car のコンストラクタが呼ばれました。

— Bicycle オブジェクトの生成 —
抽象クラス Vehicle のコンストラクタが呼ばれました: GIANT TCR
具象クラス Bicycle のコンストラクタが呼ばれました。

— メソッドの呼び出し —
これは 2021年製 の 白 の プリウス です。
プリウス のエンジンがキュルキュル…ブォン!とかかりました。
Beep beep!
プリウス の走行距離が 100km 増えました (Vehicleクラス).

これは 2022年製 の ロードバイク である GIANT TCR です。
GIANT TCR にエンジンはありません。
Ching ching!
GIANT TCR の走行距離が 10km 増えました (Vehicleクラス).
“`

抽象クラスを使うことで、「すべての乗り物(Vehicle)は、自身の説明(getDescription)を持ち、エンジンをかける(startEngine)という概念的な操作を持つべきだ」という共通の「契約」を定義しつつ、その具体的な実装は各子クラスに任せることができます。また、increaseMileage のように共通の実装を持つメソッドを定義することも可能です。

インターフェース(Interface)

インターフェースは、クラスが実装すべきメソッドのシグネチャのみを定義するものです。抽象クラスと異なり、インターフェースはプロパティや具象メソッドを一切持つことができません(PHP 7.1以降で定数を定義することは可能になりました)。

インターフェースは、特定の機能や役割を果たすための「契約」を定義するために使われます。「このインターフェースを実装するクラスは、これらのメソッドを必ず持つ」ということを保証します。

PHPでインターフェースを定義するには interface キーワードを使い、クラスがインターフェースを実装するには implements キーワードを使います。一つのクラスは複数のインターフェースを実装することができます(PHPはクラスの多重継承はできませんが、インターフェースの多重実装は可能です)。

“`php

model = $model;
}

public function getModel(): string { return $this->model; }
public function getMileage(): int { return $this->mileage; }

// Runnable インターフェースの run メソッドを実装
public function run(int $distance): void
{
if ($distance > 0) {
$this->mileage += $distance;
echo $this->model . ” が ” . $distance . “km 走行しました。\n”;
} else {
echo $this->model . “: 走行距離は正の値で指定してください。\n”;
}
}

// Runnable インターフェースの stop メソッドを実装
public function stop(): void
{
echo $this->model . ” が停止しました。\n”;
}

// Honkable インターフェースの honk メソッドを実装
public function honk(): void
{
echo “Beep beep!\n”;
}

// Car 固有のメソッド
public function refillFuel(): void
{
echo $this->model . ” に給油しました。\n”;
}
}

// Runnable インターフェースを実装する Person クラス
class Person implements Runnable
{
private $name;
private $distanceCovered = 0;

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

public function getName(): string { return $this->name; }
public function getDistanceCovered(): int { return $this->distanceCovered; }

// Runnable インターフェースの run メソッドを実装
public function run(int $distance): void
{
if ($distance > 0) {
$this->distanceCovered += $distance;
echo $this->name . ” が ” . $distance . “km 走りました。\n”;
} else {
echo $this->name . “: 走行距離は正の値で指定してください。\n”;
}
}

// Runnable インターフェースの stop メソッドを実装
public function stop(): void
{
echo $this->name . ” が立ち止まりました。\n”;
}

// Person 固有のメソッド
public function eat(): void
{
echo $this->name . ” が食事をしました。\n”;
}
}

// インターフェースの型ヒントを利用する関数
// この関数は Runnable インターフェースを実装しているオブジェクトなら、
// Car であろうと Person であろうと受け取ることができる
function makeObjectRun(Runnable $runnableObject, int $distance) {
$runnableObject->run($distance);
}

echo “— Car と Person オブジェクトの生成 —\n”;
$myCar = new Car(‘セダン’);
$runner = new Person(‘山田さん’);

echo “\n— インターフェースによる多態性の例 —\n”; // 後述の多態性セクションで詳しく
makeObjectRun($myCar, 50);
makeObjectRun($runner, 5);

echo “\n— オブジェクト固有のメソッド呼び出し —\n”;
$myCar->stop();
$myCar->refillFuel(); // Car 固有のメソッド

$runner->stop();
$runner->eat(); // Person 固有のメソッド

// インターフェースのメソッドも呼び出せる
$myCar->honk(); // Honkable インターフェースのメソッド

?>

“`

実行結果:

“`
— Car と Person オブジェクトの生成 —

— インターフェースによる多態性の例 —
セダン が 50km 走行しました。
山田さん が 5km 走りました。

— オブジェクト固有のメソッド呼び出し —
セダン が停止しました。
セダン に給油しました。
山田さん が立ち止まりました。
山田さん が食事をしました。
Beep beep!
“`

インターフェースを使うことで、「走る (Runnable)」という能力を持つオブジェクト(CarもPersonも)に対して、共通の run メソッドや stop メソッドを呼び出すことができるようになります。これは、オブジェクトの種類(CarかPersonか)を意識せずに、そのオブジェクトが持つ「特定の能力」に基づいて操作を行うことを可能にします。これが、次に説明する「多態性」の重要な基盤となります。

抽象クラスとインターフェースの使い分けの目安としては、以下のようになります。

  • 抽象クラス: IS-A関係(継承関係)があり、共通する状態(プロパティ)や共通のデフォルトの振る舞い(具象メソッド)を持ちつつ、一部の振る舞いを子クラスに実装させたい場合に使う。単一継承。
  • インターフェース: クラスが持つべき「能力」や「契約」を定義したい場合に使う。異なるクラス階層に属するオブジェクト間でも、共通のインターフェースを実装していれば同じように扱えるようになる。多重実装が可能。

どちらも「抽象化」を実現し、コードの柔軟性や拡張性を高める上で非常に役立ちます。

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

オブジェクト指向の四つ目の原則が「ポリモーフィズム」、または「多態性」です。これは「多くの形を持つ」という意味で、同じ名前のメソッド(あるいは同じ型の変数)が、それらが適用されるオブジェクトの種類によって異なる振る舞いをすることを指します。

多態性は、主に以下の二つの方法で実現されます。

  1. メソッドのオーバーライド(Override): 継承関係にあるクラスにおいて、子クラスが親クラスのメソッドを再定義すること。同じメソッド名でも、親クラスのオブジェクトで呼び出した場合と子クラスのオブジェクトで呼び出した場合とで、異なる処理が実行されます。
  2. インターフェースの実装(Implementation): 異なるクラスが同じインターフェースを実装し、インターフェースで定義されたメソッドをそれぞれ独自に実装すること。インターフェースの型としてオブジェクトを扱う場合、呼び出されるメソッドは実際のオブジェクトのクラスで実装されたものになります。

先の例で、これらの多態性がどのように機能するかを見てみましょう。

オーバーライドによる多態性

継承のセクションで見た Car, PassengerCar, Truck の例を思い出してください。Truck クラスは Car クラスの drive メソッドをオーバーライドしていました。

“`php
// Truck クラスの drive メソッド (オーバーライド)
class Truck extends Car {
// … (他のコード) …
public function drive(int $distance)
{
if ($distance > 0) {
parent::drive($distance); // 親のメソッドを呼び出しつつ
echo $this->getModel() . ” は重い荷物を積んで走行しました (Truckクラス).\n”; // 独自の処理を追加
} else {
parent::drive($distance);
}
}
// … (他のコード) …
}

$myTruck = new Truck(‘ふそう’, ‘緑’, 2018, 10000);
$myTruck->drive(50); // Truck クラスでオーバーライドされた drive メソッドが実行される
“`

また、PassengerCarTruck は、Car クラスの displayBasicInfo メソッドを呼び出しつつ、独自の displayInfo メソッドを持っていました。そして、これらの子クラスのオブジェクトを Car 型として扱う場合(例えば、showVehicleInfo(Car $vehicle) のように型ヒントで Car を指定する場合)、Car クラスに存在する public または protected なメソッドであれば呼び出すことができます。

“`php
// Car クラスを型ヒントとして使う関数
function showModelInfo(Car $vehicle) {
// Car クラスに定義されている getModel() メソッドは、
// $vehicle が PassengerCar でも Truck でも呼び出せる
// そして、呼び出される getModel() メソッドは、
// $vehicle が Car, PassengerCar, Truck のいずれであろうと、
// それぞれのクラス(ここでは Car クラスで定義されているもの)のものが実行される。
echo “モデル: ” . $vehicle->getModel() . “\n”;
}

$myCar = new PassengerCar(‘プリウス’, ‘白’, 2021, 5);
$myTruck = new Truck(‘ふそう’, ‘緑’, 2018, 10000);

showModelInfo($myCar); // 出力: モデル: プリウス
showModelInfo($myTruck); // 出力: モデル: ふそう
“`

このように、親クラスの型として子クラスのオブジェクトを扱う際に、呼び出されるメソッドが実際のオブジェクトのクラスに基づいていることが、オーバーライドによる多態性です。

インターフェースによる多態性

インターフェースのセクションで見た Runnable インターフェースと、それを実装する Car クラス、Person クラスの例を思い出してください。makeObjectRun 関数は、引数に Runnable 型のオブジェクトを受け取るように定義されていました。

“`php
// Runnable インターフェースの型ヒントを利用する関数
function makeObjectRun(Runnable $runnableObject, int $distance) {
// $runnableObject は Runnable インターフェースを実装していることは保証されている
// したがって、run() メソッドを呼び出すことができる
$runnableObject->run($distance);
}

$myCar = new Car(‘セダン’);
$runner = new Person(‘山田さん’);

makeObjectRun($myCar, 50); // $runnableObject の実際の中身は Car オブジェクト
makeObjectRun($runner, 5); // $runnableObject の実際の中身は Person オブジェクト
“`

makeObjectRun 関数の内部では、受け取った $runnableObjectCar オブジェクトなのか Person オブジェクトなのかを知る必要はありません。ただ、Runnable インターフェースを実装しているという事実だけを知っていれば、自信を持って $runnableObject->run($distance) と呼び出すことができます。そして、この run メソッド呼び出しは、実際に $runnableObject に格納されているオブジェクトが Car であれば Car クラスの run メソッドが、Person であれば Person クラスの run メソッドが実行されます。

これがインターフェースによる多態性です。共通のインターフェースを介して、異なるクラスのオブジェクトを同じように扱うことができます。これにより、コードの柔軟性が大幅に向上します。例えば、将来的に「Runnable」な新しいクラス(例: Animal クラス)を追加した場合でも、そのクラスが Runnable インターフェースを実装していれば、既存の makeObjectRun 関数はその新しいオブジェクトに対してもそのまま機能します。

多態性は、オブジェクト指向の大きな強みの一つです。コードが特定のクラスに強く依存するのではなく、より抽象的な概念(親クラスやインターフェース)に依存するようになるため、コードの変更や拡張が容易になります。

発展的な概念(基礎の次に知りたいこと)

この記事はオブジェクト指向の「第一歩」ですが、基礎概念を理解した上で、さらに知っておくとPHPでのOOP開発の幅が広がる概念もいくつか存在します。ここでは簡単に触れておきます。

  • 静的プロパティと静的メソッド (static): クラスのインスタンス(オブジェクト)ではなく、クラス自体に紐づくプロパティやメソッドです。:: 演算子を使って ClassName::staticPropertyClassName::staticMethod() のようにアクセスします。シングルトンパターンやファクトリーメソッドなどで利用されることがあります。
  • 定数 (const): クラス内で不変の値を持つプロパティのようなものです。const SOME_VALUE = 123; のように定義し、ClassName::SOME_VALUE のようにアクセスします。
  • トレイト (trait): PHP 5.4 で導入された、コード再利用のための仕組みです。クラスを継承するのではなく、トレイトを「use」することで、トレイトに定義されたメソッドなどをクラスに取り込むことができます。多重継承のような問題を避けつつ、複数のクラス間で共通の機能を持たせたい場合に有効です。
  • 名前空間 (namespace): クラス名や関数名などの名前の衝突を防ぐための仕組みです。特にライブラリやフレームワークを組み合わせる大規模なプロジェクトでは必須となります。

これらの概念は、基礎をしっかり学んだ上で、必要に応じて学んでいくのが良いでしょう。まずはクラス、オブジェクト、プロパティ、メソッド、そしてカプセル化、継承、抽象化、多態性の4原則をマスターすることに集中しましょう。

OOP実践のヒントと次のステップ

オブジェクト指向の基礎を理解した今、PHPで実際にOOPを使ってコードを書き始めるためのヒントと、今後の学習への示唆をいくつかご紹介します。

  • 小さなことから始める: まずは簡単なクラスを作成し、オブジェクトを生成してプロパティやメソッドを操作する練習から始めましょう。身の回りのモノ(犬、猫、本、電化製品など)をモデルにしてクラスを設計してみるのがおすすめです。
  • リファクタリングに挑戦する: すでに書いた手続き型のコードを、オブジェクト指向の考え方を使って書き直してみましょう。例えば、データベース操作を行う一連の関数を DatabaseManager クラスとしてまとめ直すなどです。
  • デザインパターンを学ぶ: オブジェクト指向には、共通の設計上の問題を解決するための「デザインパターン」という考え方があります。シングルトン、ファクトリー、オブザーバーなど、基本的なパターンを学ぶことで、より堅牢で保守性の高いコードを書くヒントが得られます。ただし、これは基礎を固めた後のステップです。
  • モダンなPHPフレームワークに触れる: LaravelやSymfonyといった現代的なPHPフレームワークは、強力にオブジェクト指向を活用しています。これらのフレームワークのコードを読むことは、実践的なOOPの学習になります。また、フレームワークの機能を利用する上でも、OOPの知識が不可欠です。
  • 他の人のコードを読む: オープンソースのPHPプロジェクトやライブラリのコードを読んでみましょう。他の開発者がどのようにOOPを使っているかを学ぶことができます。

オブジェクト指向は、一度にすべてを理解しようとすると難しく感じることがあります。しかし、焦らず、今回学んだ基礎概念を一つずつ消化し、実際にコードを書いて試してみることが大切です。特に、クラス、オブジェクト、プロパティ、メソッド、そしてカプセル化、継承、抽象化、多態性のそれぞれの概念と、それらがなぜ必要なのか、どのようにコードに反映されるのかを、例を通じて繰り返し確認してください。

まとめ

この記事では、PHPオブジェクト指向プログラミングの「第一歩」として、以下の基礎知識を詳しく解説しました。

  • オブジェクト指向プログラミング (OOP) とは: 現実世界のモノをモデルに、データと操作を一体として扱う考え方。保守性、再利用性、拡張性などのメリットがある。
  • クラス (Class): オブジェクトの設計図。プロパティ(状態)とメソッド(振る舞い)を定義する。
  • オブジェクト (Object/Instance): クラスから生成された実体。それぞれが独立したプロパティの値を持つ。new キーワードで生成する。
  • プロパティ (Property): オブジェクトの状態を表す変数。オブジェクトの変数名と -> 演算子でアクセスする。
  • メソッド (Method): オブジェクトが行える操作を表す関数。メソッド内でオブジェクト自身を参照するには $this を使う。
  • コンストラクタ (__construct): オブジェクト生成時に自動実行される特殊なメソッド。プロパティの初期化などに利用する。
  • アクセス修飾子 (public, protected, private): プロパティやメソッドへのアクセス範囲を制御する。カプセル化の実現に不可欠。
  • カプセル化 (Encapsulation): データと操作を一つにまとめ、内部実装を隠蔽すること。アクセス修飾子やゲッター/セッターで実現し、データの保護とコードの変更への耐性を高める。
  • 継承 (Inheritance): 既存クラスの機能を引き継ぎ、新しいクラスを定義すること。extends キーワードを使用し、コードの再利用や階層化に役立つ。parent:: で親クラスのメソッドを呼び出せる。
  • 抽象化 (Abstraction): 本質的な部分だけを取り出し、詳細を隠すこと。
    • 抽象クラス (abstract class): インスタンス化できず、継承されることを前提としたクラス。抽象メソッド(実装なし)と具象メソッド(実装あり)を持てる。
    • インターフェース (interface): 実装を持たないメソッドの集合(契約)を定義する。implements でクラスに実装させ、特定の能力を持つことを保証する。多重実装が可能。
  • ポリモーフィズム (Polymorphism): 同じ名前のメソッドが、オブジェクトの種類によって異なる振る舞いをすること。オーバーライドやインターフェースの実装によって実現し、コードの柔軟性を高める。

これらの基礎概念は、PHPだけでなく他の多くのオブジェクト指向言語にも共通するものです。一度しっかりと理解すれば、PHP以外の言語を学ぶ際にも役立つはずです。

オブジェクト指向は、最初は少し難しく感じるかもしれませんが、慣れればより大規模で複雑なアプリケーションを効率的に開発するための強力な武器となります。この記事が、皆さんのPHPオブジェクト指向プログラミング学習の確かな第一歩となることを願っています。

Happy Coding!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール