---
# vim: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab:
layout: "@/layouts/MarkdownLayout.astro"
---
import Toc from "../../components/Toc.astro";
import ExampleWrapper from "@/components/ExampleWrapper.astro";
import RenderFile from "@/components/RenderFile";
import RenderHtml from "@/components/RenderHtml";
import Details from "@/components/Details.astro";
export const title = "オブジェクト指向とクラス";
# {title}
## TOC
## 大事な用語
- Class (クラス)
- インスタンス
- 継承
## データのまとまり
プログラムを組むとき、データのまとまりを扱うことがよくあります。
- 設定ファイル
- データベースから取得したデータ
- 入力フォームから送られてきたデータ
- ...etc
この中でも入力フォームは、開発者自身であってもどのような値が入ってくるかわからなくなることが多いです。
このようなフォームがあったとしても、
<RenderFile path="public/sample/tips/object-oriented/form.html" />
データを受け取る側は、どのような値が入ってきているのかわかりません。
これを知るために、とりあえず変数の中身を表示する処理を書いてみたり、
```php file=public/sample/tips/object-oriented/confirm.php
```
<br />
もととなるHTMLの`name`属性を直接確認してみたりすることもあるでしょう。
<Details summary="HTMLを見る">
```php file=public/sample/tips/object-oriented/form.html collapse={1-153, 184-188} /name="[a-zA-Z0-9]+"/
```
</Details>
<br />
これらの確認を経て、送られてくるデータには以下のものがあるとはじめてわかります。
- name
- email
- gender
- message
しかし、予めどのようなデータが入ってくるのかを把握したいものです。
今回の例のような入力フォームであれば、HTMLの変更によって意図しないデータが入ってくる可能性もありますし、そもそもHTMLを見ないとどのようなデータが入ってくるのかわからないのは、あまり良い状態とは言えません。
これを解決するために、あるデータの集まりに対して、具体的になにがあるのかを説明するようなアプローチが存在します。
その最も原始的な方法が、 `構造体(struct)` や `クラス(class)` を使うことです。
これはHTMLから送られてくるデータ全体をひとつのまとまりとして扱う考え方です。
この構造の中に、具体的に何が入ってくるのかを定義することができます。
```php
<?php
/**
* HTMLから送られてくるデータの構造を定義するクラス
*/
class FormField {
public $name;
public $email;
public $gender;
public $message;
}
```
この`FormField`クラスでは、この構造の中に、`name`、`email`、`gender`、`message`というデータが入ってくることを定義しています。
なお、これらクラスの中にあるデータは、以下のような様々な呼び方があります。
- プロパティ
- メンバー変数
- フィールド
- 属性
このページの中では、クラスの中で定義される変数を、**プロパティ**と呼ぶことにします。
次の章では、クラスの使い方について説明していきます。
## クラスの使い方
### 構造を定義する (メンバー変数)
[データのまとまり](#データのまとまり) で示したように、そのまとまりに対して具体的にどのようなデータが入ってくるのかを定義することができます。
これはデータ構造を表すための設計図のようなものです。
このクラスは通常の型と同じように、変数の型として使うことができます。
ところで通常の型は代入した時点で、その型の値が入ってきます。
```php
<?php
// numberという型で、値は123
$data = 123;
// stringという型で、値は"hello"
$data = "hello";
```
<br />
一方でクラスは、データ構造を定義するための型で、設計図のようなものだと説明しました。
これを具体的なデータとして使うためには、`new` というキーワードを使って変数に代入する必要があります。
```php collapse={2-8}
<?php
class FormField {
public $name;
public $email;
public $gender;
public $message;
}
// $変数 = new クラス名();
$formField = new FormField();
```
<br />
この `new クラス名()` で作られたものを、クラスの **インスタンス (instance)** と呼びます。
ところでこの時点では、作られたインスタンスには、特に初期値を設定していません。
なにかしらの手段で、値を入れてあげる必要があります。
```php collapse={2-8}
<?php
class FormField {
public $name;
public $email;
public $gender;
public $message;
}
// $変数 = new クラス名();
$formField = new FormField();
var_dump($formField);
// この時点では、空の値であることを示すNULLが入っている (言語によって違う)
// object(FormField)#1 (4) {
// ["name"]=>
// NULL
// ["email"]=>
// NULL
// ["gender"]=>
// NULL
// ["message"]=>
// NULL
// }
$formField->name = $_POST["name"];
$formField->email = $_POST["email"];
$formField->gender = $_POST["gender"];
$formField->message = $_POST["message"];
var_dump($formField);
// object(FormField)#1 (4) {
// ["name"]=>
// string(5) "Alice"
// ["email"]=>
// string(17) "alice@example.com"
// ["gender"]=>
// string(3) "woman"
// ["message"]=>
// string(11) "Hello world"
// }
```
<br />
これで、クラスを使うための最低限の準備ができました。
### 振る舞いを定義する (メソッド / 関数)
クラスでは、インスタンスが持っているデータを活用して、なにかしらの振る舞いを定義することができます。
これを **メソッド (method)** と呼びます。
例として、`gender` に値を設定するメソッドを定義してみましょう。
```php {8-11}
<?php
class FormField {
public $name;
public $email;
public $gender;
public $message;
public function setGender($gender) {
// 自身のgenderプロパティに、引数の値を代入する
$this->gender = $gender;
}
}
$formField = new FormField();
$formField->setGender("man");
echo $formField->gender . PHP_EOL;
// => man
$formField->setGender("woman");
echo $formField->gender . PHP_EOL;
// => woman
```
<br />
`$this` という変数が出てきました。
通常クラスのインスタンスは、`$formField = new FormField();` のように、変数に代入されます。
しかしこの `$formField` という変数はクラスの外で定義されている変数で、クラスの中からはどのような変数名で代入されるのかわかりません。
とはいえクラスの中から、自分自身が持っているデータにアクセスしたいことはよくあります。
このときに、クラスのメソッド内でのみ使える特別な変数 `$this` を使うことで、クラスの中から自分自身のインスタンスを指すことができます。
この`$this`を経由して、自身が持っている `gender` というプロパティにアクセス、値を代入するということができます。
<br />
<br />
値を設定するメソッドを定義したので、次は値を活用するメソッドも定義してみましょう。
```php {12-38}
<?php
class FormField {
public $name;
public $email;
public $gender;
public $message;
public function setGender($gender) {
$this->gender = $gender;
}
public function reply_template() {
$gender_display = null;
if ($this->gender === "man") {
$gender_display = "男性";
} else if ($this->gender === "woman") {
$gender_display = "女性";
} else {
$gender_display = "不明";
}
$body = "この度はお問い合わせいただきまして、誠にありがとうございます。
いただいた内容を確認の上、改めてご連絡いたします。
ご入力内容
お名前: {$this->name}
性別: {$gender_display}
メールアドレス: {$this->email}
お問い合わせ内容
---
{$this->message}
---
";
return $body;
}
}
$formField = new FormField();
$formField->name = "Alice";
$formField->email = "alice@example.com";
$formField->gender = "woman";
$formField->message = "Hello world";
$template = $formField->reply_template();
// この度はお問い合わせいただきまして、誠にありがとうございます。
// いただいた内容を確認の上、改めてご連絡いたします。
//
// ご入力内容
//
// お名前: Alice
// 性別: 女性
// メールアドレス: alice@example.com
//
// お問い合わせ内容
// ---
// Hello world
// ---
$formField->name = "Bob";
$template = $formField->reply_template();
// 名前がBobに変わっていることがわかる
```
### 特別なメソッド
クラスの中で、特別な意味を持つメソッドが存在します。
この中では、**constructor (コンストラクタ)** と呼ばれるメソッドが特に重要です。
[クラスの使い方](#クラスの使い方) の章で、クラスをインスタンス化するためには、`new クラス名()` という構文を使う必要があると説明しました。
その書き方は、クラス名の後ろに、括弧 `()` をつける形になっています。
まるで関数を呼び出すような書き方になっていますが、これがコンストラクタと呼ばれる特別なメソッドの呼び出しになっています。
具体的に見てみましょう。
```php
<?php
class User {
public $name;
public function __construct() {
echo "newされました";
}
}
$user = new User(); // __construct() が呼び出される
// => newされました
```
このコンストラクタには、引数を定義することもできます。
その多くの場合、インスタンスを作るときにプロパティの初期値を設定するために使われます。
```php
<?php
class User {
public $name;
public function __construct($name) {
echo "newされました";
$this->name = $name;
}
}
// 引数が指定されていないので、エラーになる
// $user = new User();
$user = new User("Alice"); // nameプロパティに"Alice"が入ったインスタンスが代入される
echo $user->name . PHP_EOL;
// => Alice
```
<br />
なお、このコンストラクタは言語によって書き方が異なります。
<Details summary="他の言語のコンストラクタを見てみる">
```java title=Java
class User {
public String name;
// クラス名がそのままコンストラクタの名前になる
public User() {
System.out.println("newされました");
}
}
```
<br />
```python title=Python
class User:
# コンストラクタは __init__ という名前になる
def __init__(self):
print("newされました")
```
<br />
</Details>
### 派生を作る (継承)
クラスには、**継承 (inheritance)** という機能があります。
これはあるクラスをもとにして、さらに新しいクラスを作ることができる機能です。
継承をすることにより、もとのクラスの一部分だけを変更したり、もとのクラスの機能を拡張したりすることができます。
```php
<?php
class User {
public $name;
public function __construct($name) {
$this->name = $name;
}
public function greet(User $target) {
echo "こんにちは、{$target->name}さん!";
}
}
$owner = new User("オーナー");
$user1 = new User("Bob");
$user1->greet($owner);
// => こんにちは、オーナーさん!
class CheerfulUser extends User {
public function greet(User $target) {
echo "やあ、{$target->name}さん!";
}
}
$user2 = new CheerfulUser("Charlie");
$user2->greet($owner);
// => やあ、オーナーさん!
class PoliteUser extends User {
public function greet(User $target) {
super::greet($target); // Userクラスのgreetメソッドを呼び出す
echo "今日もいい天気ですね!";
}
}
$user3 = new PoliteUser("Dave");
$user3->greet($owner);
// => こんにちは、オーナーさん!今日もいい天気ですね!
class RudeUser extends User {
public function greet(User $target) {
echo "おい、{$target->name}!";
}
public function shout(User $target) {
echo "うるせぇ、{$target->name}!";
}
}
$user4 = new RudeUser("Eve");
$user4->greet($owner);
// => おい、オーナー!
$user4->shout($owner);
// => うるせぇ、オーナー!
```
### 敢えてアクセス制限をかける (アクセス修飾子)
ここまで読み進めてきた人に取っては、ソースコードに含まれる `public` というキーワードが気になっているかもしれません。
これは、どこからでもアクセスすることを許可するという意味の **アクセス修飾子 (access modifier)** と呼ばれるものです。
アクセス修飾子には、いくつかの種類がありますが、主に以下の3つが使われます。
| 種類 | 説明 |
| :-- | :-- |
| `public` | どこからでもアクセスできる |
| `protected` | クラスの中と、継承したクラスの中からアクセスできる |
| `private` | クラスの中からのみアクセスできる |
具体的に見てみましょう。
あるユーザーに対応するクラスがあったとして、そのメソッドに成人かどうかを判定する `isAdult()` というメソッドがあったとします。
また、`birthday()` を呼び出すことが、誕生日を迎えることを表すとします。
```php
<?php
class User {
public $name;
public $age;
public function __construct(array $values) {
$this->name = $values["name"];
$this->age = $values["age"];
}
public function birthday() {
$this->age += 1;
}
public function isAdult() {
if ($this->age >= 20) {
echo "{$this->age}歳なので、成人です";
} else {
echo "{$this->age}歳なので、未成年です";
}
}
}
$user = new User([ "name" => "Alice", "age" => 18 ]);
$user->isAdult();
// => 18歳なので、未成年です
// 2回誕生日を迎える
$user->birthday();
$user->birthday();
$user->isAdult();
// => 20歳なので、成人です
```
<br />
今、すべてのアクセス修飾子が `public` になっています。
このとき、どのような不都合があるでしょうか?
```php collapse={6-22} add={"ageはpublicなので、外部から代入できる": 37-38}
<?php
class User {
public $name;
public $age;
public function __construct(array $values) {
$this->name = $values["name"];
$this->age = $values["age"];
}
public function birthday() {
$this->age += 1;
}
public function isAdult() {
if ($this->age >= 20) {
echo "{$this->age}歳なので、成人です";
} else {
echo "{$this->age}歳なので、未成年です";
}
}
}
$user = new User([ "name" => "Alice", "age" => 18 ]);
$user->isAdult();
// => 18歳なので、未成年です
// 2回誕生日を迎える
$user->birthday();
$user->birthday();
$user->isAdult();
// => 20歳なので、成人です
$user->age = 10; // 年齢詐称
$user->isAdult();
// => 10歳なので、未成年です
```
<br />
なんと年齢を詐称することができてしまいました。
これができるということは、未成年にも関わらずにお酒を飲んだり、タバコを吸ったりすることを許してしまうことになります。
これをさせないために、`age` プロパティを `private` にしてみましょう。
```php collapse={6-22} "private" {"ageはprivateなので、外部から操作できない": 37-38}
<?php
class User {
private $name;
private $age;
public function __construct(array $values) {
$this->name = $values["name"];
$this->age = $values["age"];
}
public function birthday() {
$this->age += 1;
}
public function isAdult() {
if ($this->age >= 20) {
echo "{$this->age}歳なので、成人です";
} else {
echo "{$this->age}歳なので、未成年です";
}
}
}
$user = new User([ "name" => "Alice", "age" => 18 ]);
$user->isAdult();
// => 18歳なので、未成年です
// 2回誕生日を迎える
$user->birthday();
$user->birthday();
$user->isAdult();
// => 20歳なので、成人です
$user->age = 10; // Error: Cannot access private property User::$age
```
<br />
これで、誕生日を迎える以外の方法で、年齢を変更することができなくなりました。
ところで、`User`というクラスから、派生したクラスが作られたとします。
そこに、`greet()` というメソッドを定義して、ユーザーに挨拶させたいとします。
```php add={25-29} "private"
<?php
class User {
private $name;
private $age;
public function __construct(array $values) {
$this->name = $values["name"];
$this->age = $values["age"];
}
public function birthday() {
$this->age += 1;
}
public function isAdult() {
if ($this->age >= 20) {
echo "{$this->age}歳なので、成人です";
} else {
echo "{$this->age}歳なので、未成年です";
}
}
}
class ExtendedUser extends User {
public function greet() {
echo "こんにちは、{$this->name}さん!"; // Undefined property: User::$name
}
}
```
<br />
これはエラーになります。
なぜなら、基底クラスである `User` クラスの `name` プロパティは、アクセス修飾子が `private` になってなっています。
そのため、継承先のクラスではこのプロパティにアクセスすることができないからです。
これを解決するために、アクセス修飾子を `protected` にしてみましょう。
```php "protected" {31-33} {"protectedなので、関係のない場所からは操作できない": 45-46}
<?php
class User {
protected $name;
protected $age;
public function __construct(array $values) {
$this->name = $values["name"];
$this->age = $values["age"];
}
public function birthday() {
$this->age += 1;
}
public function isAdult() {
if ($this->age >= 20) {
echo "{$this->age}歳なので、成人です";
} else {
echo "{$this->age}歳なので、未成年です";
}
}
}
class ExtendedUser extends User {
public function greet() {
echo "こんにちは、{$this->name}さん!";
}
}
$user = new ExtendedUser([ "name" => "Alice", "age" => 18 ]);
$uset->greet();
// => こんにちは、Aliceさん!
$user->isAdult();
// => 18歳なので、未成年です
// 2回誕生日を迎える
$user->birthday();
$user->birthday();
$user->isAdult();
// => 20歳なので、成人です
$user->age = 10; // Error: Cannot access protected property User::$age
```
<br />
これで、継承先のクラスからは `name` プロパティにアクセスすることができるようになりました。
`age`も同様に `protected` にしているので、継承先のクラスからはアクセスできますが、関係のない場所からはアクセスできないようになっています。
### データを共有する (static 1)
### データに依存しない処理を定義する (static 2)
## よく見るコード例
### 初期化のための静的メソッド
初期値となるデータを渡すと、クラスのインスタンスを作るような静的メソッド
```php {9-22}
<?php
class FormField {
protected $name;
protected $email;
protected $gender;
protected $message;
public static function fromValues($values) {
if (!is_array($values)) {
throw new Exception("values must be an array");
}
$instance = new FormField();
$instance->name = $values["name"];
$instance->email = $values["email"];
$instance->gender = $values["gender"];
$instance->message = $values["message"];
return $instance;
}
}
// $_POSTの中身をもとに、FormFieldクラスのインスタンスを作る
$formField = FormField::fromValues($_POST);
```
### 別の型に変換するためのメソッド
入力フォームの情報を、そのままユーザー情報に変換する処理
```php {3-24, 46-57}
<?php
class User {
protected $id = null;
protected $name;
protected $email;
protected $gender;
public static function fromValues($values) {
if (!is_array($values)) {
throw new Exception("values must be an array");
}
$instance = new User();
// 代入の処理は省略
return $instance;
}
public function save() {
// データベースに保存する処理
}
}
class FormField {
protected $name;
protected $email;
protected $gender;
protected $message;
public static function fromValues($values) {
if (!is_array($values)) {
throw new Exception("values must be an array");
}
$instance = new FormField();
$instance->name = $values["name"];
$instance->email = $values["email"];
$instance->gender = $values["gender"];
$instance->message = $values["message"];
return $instance;
}
public function toUser() {
$values = [
"name" => $this->name,
"gender" => $this->gender,
"email" => $this->email,
];
$user = new User();
$user->fromValues($values);
return $user;
}
}
// $_POSTの中身をもとに、FormFieldクラスのインスタンスを作る
$formField = FormField::fromValues($_POST);
// FormFieldクラスが持つ情報で、Userクラスのインスタンスを作る
$user = $formField->toUser();
$user->save();
```
---
```c
#include <stdio.h>
typedef char* str;
struct User {
str name;
int age;
};
int main() {
User me = {
.name = "Alice",
.age = 30
};
printf("name: %s\n", me.name);
// name: Alice
printf("age: %d\n", me.age);
// age: 30
return 0;
}
```
```php
<?php
class User {
public $name;
public $age;
}
$user = new User();
$user->name = "Alice";
$user->age = 30;
echo $user->name . PHP_EOL;
// Alice
echo $user->age . PHP_EOL;
// 30
```
```php
<?php
class User {
public $name;
public $age;
public function getName() {
return $this->name;
}
}
$user = new User();
$user->name = "Alice";
$user->age = 30;
echo $user->name . PHP_EOL;
echo $user->getName() . PHP_EOL;
// Alice
echo $user->age . PHP_EOL;
// 30
```
```php
<?php
class User {
public $name;
public $age;
public function setName($name) {
if (!is_string($name)) {
throw new Exception("name must be a string");
}
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$user = new User();
$user->name = 9817298471982; // 文字じゃねぇ
if (!is_string($user->name)) {
throw new Exception("name must be a string");
}
$user->setName("Alice");
// ok
$user->setName(123);
// Exception: name must be a string
$user->age = 30;
echo $user->name . PHP_EOL;
echo $user->getName() . PHP_EOL;
// Alice
echo $user->age . PHP_EOL;
// 30
```
```php
<?php
class User {
// private $name;
protected $name;
public $age;
public function setName($name) {
if (!is_string($name)) {
throw new Exception("name must be a string");
}
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$user = new User();
$user->name = 9817298471982;
if (!is_string($user->name)) {
throw new Exception("name must be a string");
}
$user->setName("Alice");
// ok
$user->setName(123);
// Exception: name must be a string
$user->age = 30;
echo $user->name . PHP_EOL;
echo $user->getName() . PHP_EOL;
// Alice
echo $user->age . PHP_EOL;
// 30
```
constructor コンストラクタ
static
```php
<?php
class User {
protected $id;
protected $name;
protected $age;
public function __construct($name) {
echo "newされました";
$this->name = $name;
$this->age = 0;
}
public static function findUser($id) {
// $name を使ってDBから取ってくる
$data = [
"name" => "bob",
"age" => 35,
];
$instance = new User($data["name"]);
$instance->age = $data["age"];
return $instance;
}
public function setName($name) {
if (!is_string($name)) {
throw new Exception("name must be a string");
}
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$user = new User("Alice");
$bob = User::findUser(1);
$user->name = 9817298471982;
if (!is_string($user->name)) {
throw new Exception("name must be a string");
}
$user->setName("Alice");
// ok
$user->setName(123);
// Exception: name must be a string
$user->age = 30;
echo $user->name . PHP_EOL;
echo $user->getName() . PHP_EOL;
// Alice
echo $user->age . PHP_EOL;
// 30
```
```php
<?php
class User {
protected $id;
protected $user;
protected $age;
public function __construct($name) {
echo "私は {$name} です";
$this->name = $name;
}
public function setName($name) {
if (!is_string($name)) {
throw new Exception("name must be a string");
}
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$user1 = new User("Alice");
// 私は Alice です
class ParttimeUser extends User {
public function __construct($name) {
echo "吾輩は {$name} です";
$this->name = $name;
}
}
$user2 = new ParttimeUser("Bob");
// 吾輩は Bob です
$user2->setName("George");
```
スマホでボリュームを上げ下げしたいとき、ボタンを押させる。
中身に触れさせて、この端子をさわってね みたいなことはさせない。