3-8. クラス

本章ではクラスについて学習していきます。

既にHTMLを学習している方からすると、「クラス?知ってる知ってる!」となるかと思います。
ですが、本章で学習するクラスはHTMLのクラスとは全くの別物なので、本章を飛ばさないように気をつけてください!

クラスは少々難しい概念ですが、これが理解できると他のプログラミング言語(Ruby, Python, C#など)を学習する際に飲み込みが早くなるので是非完全理解を目指していただきたい項目です。

それでは早速クラスの学習をしていきましょう。

クラスとは

そもそもクラスとはなんなのでしょうか?
クラスはモノを作る時に必要な「設計図」の考え方に近いです。

少し話は逸れますが、「バス」「スポーツカー」「ファミリーカー」この3つに共通している部分はなんでしょうか?
まずは、「車」であるということが共通してますよね。
では他の共通項目を簡単にリストアップしてみます。

  • カテゴリー
  • タイヤ
  • 座席
  • アクセル
  • ブレーキ etc…

これらの共通部分をまとめたものがクラスになります。

予め設計図を用意しておくと、その設計図を参考にしながら作るだけでいいので作る側からすると非常に楽になりますよね。
基本的にはプログラミングでも考え方は同じで、設計図があるとモノを生成するのが楽になります。

クラスを定義してみよう

PHPで設計図として認識させるには以下のような定義が必要です。

class クラス名(設計図名) {

}

今回は車のクラスを定義したいのでCarクラスを作成しましょう。
クラス名は先頭を大文字にするルールがあるので注意してください。

<?php
class Car {

}

これでCarクラスの定義ができました。
しかし、これだけでは白紙に名前をつけただけなのでもう少し記述を追加する必要があります。

もう一度「バス」「スポーツカー」「ファミリーカー」に共通する部分を確認してみましょう。

  • カテゴリー
  • タイヤ
  • 座席
  • アクセル
  • ブレーキ etc…

全てを網羅しているわけではありませんが、これだけ共通している部分がありましたね。
まずは「カテゴリー」「タイヤ」「座席」「色」この4つを変数化しクラスに組み込んでみます。
クラス内で定義する変数のことをプロパティと呼びます。
※ publicは後ほど解説します。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

変数名はなんでも構いませんが、変数名をみただけでその変数に何が代入際れているか推測できる名前が理想的ですね。
今回定義した変数には以下の値が代入されることを想定しています。

  • $category カテゴリー名
  • $tires タイヤの数
  • $seats 座席の数
  • $color 色

今までと異なり、変数に値を代入していませんね。
変数に具体的な値を代入しても良いのですが、使い回しやすさをベースに考えると変数に値を代入しない方が良いです。
$colorに”red”を代入すると、その設計図を基に作られる車はすべて赤になってしまうようなイメージです。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color = "red";
}

インスタンス

現段階では設計図を作っただけなので、その設計図を基に車を作る必要があります。
プログラムで書くと以下のようになります。

new クラス名(設計図);

クラスから作られたモノをインスタンスと呼びます。
言葉だけで理解するのは難しいのでまずは以下の画像のようなイメージを持てるようにしましょう。

Carクラスのインスタンスを生成してみましょう。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

new Car();

これでインスタンスが生成できました。
しかし、このままではインスタンスが生成されてすぐ破棄されてしまいます。
基本的にインスタンスは変数に代入して、生成したインスタンスを使い回せるようにしましょう。
この時も代入する変数名はなんでも構いません。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

$car1 = new Car();

$car1にはCarクラスのインスタンスが代入されているので、以下のプロパティを所有しています。

  • $category
  • $tires
  • $seats
  • $color

今度はこのプロパティに値を設定してみましょう。
設定方法は以下のようになります。

インスタンスが代入されている変数->プロパティ = 値;

矢印に見える -> この記号はアロー演算子と言います。
基本的に〇〇の中から〇〇を呼び出すという時に使われます。

Carクラスのインスタンスが代入されている変数は$car1です。
その$car1のcategoryプロパティに”バス”という文字列をセットするプログラムは以下のようになります。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

$car1 = new Car();
$car1->category = "バス";

他のプロパティにも値をセットしていきます。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

それではもう一つインスタンスを生成してみましょう。

まずはインスタンスを生成して、そのインスタンスを変数に代入するんでしたね。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();

インスタンスの生成方法が同じなので$car1と$car2は同じように見えますよね。
しかしこれは内部的に全くの別物として判断されます。
パッと見は同じですが、中身は全く違うのです。
同姓同名の人が同一人物ではないのと同じようなイメージですね。

$car2に入っているインスタンスのプロパティにもそれぞれ値をセットしていきます。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();
$car2->category = "スポーツカー";
$car2->tires = 4;
$car2->seats = 2;
$car2->color = "赤";

上記までの作業を図にすると以下のようになります。

今度はプロパティにセットした値を取得します。
プロパティの値を取得する方法は非常に簡単です。

インスタンスが代入されいている変数->プロパティ;

値をセットするときよりも簡単ですね。
取得した値を表示するときは必ずechoを使用しましょう。

では、$car1と$car2のcategoryプロパティを取得して表示してみましょう。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();
$car2->category = "スポーツカー";
$car2->tires = 4;
$car2->seats = 2;
$car2->color = "赤";

echo $car1->category . "\n";
echo $car2->category . "\n";

他のプロパティの値を取得するときも方法は同じなので覚えておきましょう。

関数とメソッド

インスタンスメソッドの解説の前にメソッドについて少し触れておきます。

簡単にいうと、クラスの中に定義する関数のことです。
他のプログラミング言語では関数とメソッドの明確な違いはなかったりするのですが、PHPではクラス内に定義する関数のことをメソッドと呼ぶので覚えておいてください。

インスタンスメソッド

クラスにはプロパティ以外にもメソッドを定義することができます。
インスタンス毎に実行できるメソッドのことをインスタンスメソッドといいます。

まずは単純に文字列を出力するインスタンスメソッドをCarクラスに定義します。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function hello() {
    echo "Hello PHP\n";
  }
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();
$car2->category = "スポーツカー";
$car2->tires = 4;
$car2->seats = 2;
$car2->color = "赤";

echo $car1->category . "\n";
echo $car2->category . "\n";

インスタンスメソッドの呼び出しは以下のようになります。

インスタンスが代入されいている変数->メソッド名();

$car1と$car2に代入されているインスタンスからそれぞれhelloメソッドを呼び出してみましょう。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function hello() {
    echo "Hello PHP\n";
  }
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();
$car2->category = "スポーツカー";
$car2->tires = 4;
$car2->seats = 2;
$car2->color = "赤";

echo $car1->category . "\n";
echo $car2->category . "\n";

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

Hello PHPが2回出力されればインスタンスメソッドの呼び出しに成功しています。

今度はインスタンスのプロパティに格納されている値をインスタンスメソッドで扱えるようにします。
Carクラスにinfoメソッドを定義してください。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function hello() {
    echo "Hello PHP\n";
  }

  public function info() {
    
  }
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();
$car2->category = "スポーツカー";
$car2->tires = 4;
$car2->seats = 2;
$car2->color = "赤";

echo $car1->category . "\n";
echo $car2->category . "\n";

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

infoメソッドを呼び出すと以下のような出力になるようにします。

カテゴリー: 〇〇
カラー: 〇〇
〇〇人乗りです

この〇〇の箇所にはプロパティの値が入ります。

出力結果から逆算してinfoメソッドの処理を考えてみます。

$car1の場合

function info() {
  echo "カテゴリー: " . $car1->category . "\n";
  echo "カラー: " . $car1->color . "\n";
  echo $car1->seats . "人乗りです\n";
}

$car2の場合

function info() {
  echo "カテゴリー: " . $car2->category . "\n";
  echo "カラー: " . $car2->color . "\n";
  echo $car2->seats . "人乗りです\n";
}

プロパティの呼び出し元のインスタンスが異なるだけでその他は共通していますね。

$car1と$car2の2パターンをCarクラスの中に定義すれば、インスタンスからinfoメソッドを呼び出すことができそうですが、エラーになってしまいます。
Carクラスに先ほどの2パターンのinfoメソッドを定義すると以下のようになります。

class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function hello() {
    echo "Hello PHP\n";
  }

  public function info() {
    echo "カテゴリー: " . $car1->category . "\n";
    echo "カラー: " . $car1->color . "\n";
    echo $car1->seats . "人乗りです\n";
  }

  public function info() {
    echo "カテゴリー: " . $car2->category . "\n";
    echo "カラー: " . $car2->color . "\n";
    echo $car2->seats . "人乗りです\n";
  }
}

エラーの発生要因は2つあります。

  • クラス内に同じメソッドが存在すること。(引数の数が異なれば同じメソッド名でもエラーになりません)
  • クラス内に$car1や$car2といった未定義の変数を呼び出していること。

メソッドを一つにまとめるには$car1と$car2をどのようにして扱うかを考えなくてはなりません。

$car1と$car2の場合でそれぞれに対応したinfoメソッドを用意するとなると手間が多くなりますし、
クラスを定義していない人からするとどのような場合でエラーが起きるか分かりづらいです。

そこで自分自身のインスタンスを表す$thisを使用します。

まずはinfoメソッドを定義し直し、呼び出すところまで記述してみます。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function hello() {
    echo "Hello PHP\n";
  }

  public function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

$car1 = new Car();
$car1->category = "バス";
$car1->tires = 4;
$car1->seats = 28;
$car1->color = "白";

$car2 = new Car();
$car2->category = "スポーツカー";
$car2->tires = 4;
$car2->seats = 2;
$car2->color = "赤";

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

$car1に代入されているインスタンスからinfoメソッドを呼び出すと、
infoメソッド内で使用されている$thisと$car1はイコールになります。

$car2に代入されているインスタンスからinfoメソッドを呼び出すときも同様に、
infoメソッド内で使用されている$thisと$car2はイコールになります。

$thisは呼び出しに応じて、$thisが指すインスタンスが変化するので呼び出し元を確認する癖をつけておきましょう。

アクセス修飾子

Carクラスを定義した時に、プロパティ名やメソッド名の前にpublicを付けました。
これをアクセス修飾子といいます。
アクセス修飾子はその修飾子を付けたものが、どこからアクセスできるかを制限することができます。

private

インスタンスから呼び出しができなくなります。

class Car {
  private $category;
  private $tires;
  private $seats;
  private $color;

  public function hello() {
    echo "Hello PHP\n";
  }

  private function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

$car = new Car();

// publicなので正常に実行
$car->hello();

// privateなのでエラー
$car->category = "バス";

// privateなのでエラー
$car->info();

public

インスタンスから呼び出しが可能になります。

// 値の設定
インスタンスが代入されいている変数->プロパティ = 値;

// 値の取得
インスタンスが代入されいている変数->プロパティ;

// メソッドの呼び出し
インスタンスが代入されいている変数->メソッド名();

privateとpublicを比較すると、アクセス範囲の観点からpublicの方が優れている様に感じられるかもしれません。
しかし、全てをpublicにしておくとプロパティの値を予期せず変更してしまう恐れがあります。

class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function hello() {
    echo "Hello PHP\n";
  }

  public function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

$car = new Car();
$car->category = "バス";
$car->tiers = 4;
$car->seats = 28;
$car->color = "白";

/*
カテゴリー: バス
カラー: 白
28人乗りです
*/
$car->info();

$car->category = "スポーツカー";

/*
カテゴリー: スポーツカー
カラー: 白
28人乗りです
*/
$car->info();

上記の例では、プロパティのアクセス修飾子がpublicなため、最初に設定したプロパティの値が途中で上書きされ、
infoメソッドの出力結果が異なってしまっています。

この問題はプロパティのアクセス修飾子をprivateにして、メソッドから間接的にプロパティの値を変更することで解決できます。

class Car {
  private $category;
  private $tires;
  private $seats;
  private $color;

  public function hello() {
    echo "Hello PHP\n";
  }

  public function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }

  public function setProperties($category, $tires, $seats, $color){
    $this->category = $category;
    $this->tires = $tires;
    $this->seats = $seats;
    $this->color = $color;
  }
}

$car = new Car();

// アクセス修飾子がprivateなのでエラー
//$car->category = "バス";

$car->setProperties("バス", 4, 28, "白");

/*
カテゴリー: バス
カラー: 白
28人乗りです
*/
$car->info();

上記の例ではプロパティ全てのアクセス修飾子をprivateにしているので、今まで通りの値のセットや取得方法はエラーになってしまいます。

そこで、プロパティに値をセットするメソッド(setProperties)を定義し、引数に渡した値をそれぞれプロパティの値にするようにしています。

プロパティを直接操作できないようにして値の再代入を防ぐことはできましたが、
結局、setPropertiesメソッドを2回以上呼び出してしまうとプロパティの値が上書きされてしまいます。
この問題は次に紹介するコンストラクタを使用することで解決できます。

コンストラクタ

これまでに定義してきたhelloメソッド、infoメソッドは

  1. 変数にインスタンスを代入
  2. インスタンスが代入された変数->クラスに定義されたメソッド

という順番で呼び出していました。

helloメソッド、infoメソッドはインスタンスが代入された変数を介して何度でも実行することが可能でした。

インスタンスが代入されいている変数->hello();
インスタンスが代入されいている変数->hello();

インスタンスが代入されいている変数->info();
インスタンスが代入されいている変数->info();

これから紹介するコンストラクタはインスタンス化してから一番最初に一度だけ呼び出されるメソッドのことをいいます。
まずはコンストラクタの定義の方法を見てみましょう。

class Sample {
  public function __construct() {
    // 処理
  }
}

コンストラクタの定義方法はメソッドの定義方法と同じです。
しかし、コンストラクタを使用するときは必ず__constructという名前で定義しなければなりません。

Carクラスにコンストラクタを定義し、インスタンス化して一番最初に呼び出されるか確認してみましょう。
※ helloメソッドは不要なので削除しています

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function __construct() {
    echo "コンストラクタが実行されました";
  }

  public function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

new Car();

19行目のインスタンス化の処理が実行されると、8~10行目に定義したコンストラクタが呼び出され、
「コンストラクタが実行されました」と出力されるようになります。

コンストラクタは今まで定義したメソッドの呼び出しとは異なり、インスタンス化した直後に一度だけ呼び出されるメソッドということを覚えておきましょう。

また、コンストラクタはメソッドと同じように引数を受け取ることもできます。

<?php
class Car {
  public $category;
  public $tires;
  public $seats;
  public $color;

  public function __construct($message) {
    echo $message;
  }

  public function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

new Car("テスト");

コンストラクタに引数を定義し(8行目)、インスタンス化のタイミングでコンストラクタ内で使用したい値を渡します。(19行目)

では、このコンストラクタを使用してプロパティに値をセットできるようにします。
この時、プロパティの値は後から変更できないものとします。

<?php
class Car {
  private $category;
  private $tires;
  private $seats;
  private $color;

  public function __construct($category, $tires, $seats, $color) {
    $this->category = $category;
    $this->tires = $tires;
    $this->seats = $seats;
    $this->color = $color;
  }

  public function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

$car = new Car("バス", 4, 28, "白");
$car->info();

まずはプロパティの再代入を防ぐためにアクセス修飾子をprivateにしておきます。(3~6行目)
次にコンストラクタで引数を受け取れるようにし、渡された値をプロパティにそれぞれ代入する処理を実装しています。(8~13行目)
インスタンス化する時にプロパティにセットしたい値を渡すことで、直接プロパティを操作することなく、
コンストラクタを介してプロパティに値をセットしています。(22行目)

最後に、infoメソッドのアクセス修飾子をprivateにしてコンストラクタから呼び出せるようにします。

<?php
class Car {
  private $category;
  private $tires;
  private $seats;
  private $color;

  public function __construct($category, $tires, $seats, $color) {
    $this->category = $category;
    $this->tires = $tires;
    $this->seats = $seats;
    $this->color = $color;
    $this->info();
  }

  private function info() {
    echo "カテゴリー: " . $this->category . "\n";
    echo "カラー: " . $this->color . "\n";
    echo $this->seats . "人乗りです\n";
  }
}

$car = new Car("バス", 4, 28, "白");

// エラー
//$car->info();

これで新しいインスタンスが作成されるたびにコンストラクタが呼び出され、間接的にinfoメソッドも呼び出せるようになりました。

クラスとインスタンスは初学者にとって非常に難しい概念で、挫折しやすい部分でもあります。
しかし、この分野が理解できるだけで他のプログラミング言語のクラスとインスタンスも同時に理解していることに繋がります。
大変だとは思いますが、「難しい」「分からない」をもう少し具体的に「どこが難しい」「どこが分からない」と整理することで客観的に自分の弱点を知ることができるので是非挑戦してみてください!

コメントを残す

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