---
# 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, 179-183} /name="[a-zA-Z0-9]+"/
```
</Details>
<br />
これらの確認を経て、送られてくるデータには以下のものがあるとはじめてわかります。
- name
- gender
- message
しかし、予めどのようなデータが入ってくるのかを把握したいものです。
今回の例のような入力フォームであれば、HTMLの変更によって意図しないデータが入ってくる可能性もありますし、そもそもHTMLを見ないとどのようなデータが入ってくるのかわからないのは、あまり良い状態とは言えません。
これを解決するために、あるデータの集まりに対して、具体的になにがあるのかを説明するようなアプローチが存在します。
その最も原始的な方法が、 `構造体(struct)` や `クラス(class)` を使うことです。
これはHTMLから送られてくるデータ全体をひとつのまとまりとして扱う考え方です。
この構造の中に、具体的に何が入ってくるのかを定義することができます。
```php
<?php
/**
* HTMLから送られてくるデータの構造を定義するクラス
*/
class FormField {
public $name;
public $gender;
public $message;
}
```
この`FormField`クラスでは、この構造の中に、`name`、`gender`、`message`というデータが入ってくることを定義しています。
なお、これらクラスの中にあるデータは、以下のような様々な呼び方があります。
- プロパティ
- メンバー変数
- フィールド
- 属性
このページの中では、クラスの中で定義される変数を、**プロパティ**と呼ぶことにします。
次の章では、クラスの使い方について説明していきます。
## クラスの使い方
### 構造を定義する (メンバー変数)
[データのまとまり](#データのまとまり) で示したように、そのまとまりに対して具体的にどのようなデータが入ってくるのかを定義することができます。
これはデータ構造を表すための設計図のようなものです。
このクラスは通常の型と同じように、変数の型として使うことができます。
ところで通常の型は代入した時点で、その型の値が入ってきます。
```php
<?php
// numberという型で、値は123
$data = 123;
// stringという型で、値は"hello"
$data = "hello";
```
<br />
一方でクラスは、データ構造を定義するための型で、設計図のようなものだと説明しました。
これを具体的なデータとして使うためには、`new` というキーワードを使って変数に代入する必要があります。
```php collapse={2-8}
<?php
class FormField {
public $name;
public $gender;
public $message;
}
// $変数 = new クラス名();
$formField = new FormField();
```
<br />
この `new クラス名()` で作られたものを、クラスの **インスタンス (instance)** と呼びます。
ところでこの時点では、作られたインスタンスには、特に初期値を設定していません。
なにかしらの手段で、値を入れてあげる必要があります。
```php collapse={2-8}
<?php
class FormField {
public $name;
public $gender;
public $message;
}
// $変数 = new クラス名();
$formField = new FormField();
var_dump($formField);
// この時点では、空の値であることを示すNULLが入っている (言語によって違う)
// object(FormField)#1 (3) {
// ["name"]=>
// NULL
// ["gender"]=>
// NULL
// ["message"]=>
// NULL
// }
$formField->name = $_POST["name"];
$formField->gender = $_POST["gender"];
$formField->message = $_POST["message"];
var_dump($formField);
// object(FormField)#1 (3) {
// ["name"]=>
// string(5) "Alice"
// ["gender"]=>
// string(3) "woman"
// ["message"]=>
// string(11) "Hello world"
// }
```
<br />
これで、クラスを使うための最低限の準備ができました。
### 振る舞いを定義する (メソッド / 関数)
クラスでは、インスタンスが持っているデータを活用して、なにかしらの振る舞いを定義することができます。
これを **メソッド (method)** と呼びます。
例として、`gender` に値を設定するメソッドを定義してみましょう。
```php {7-9}
<?php
class FormField {
public $name;
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 {11-36}
<?php
class FormField {
public $name;
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->message}
---
";
return $body;
}
}
```
### 特別なメソッド
TODO: constructor
### 敢えてアクセス制限をかける (アクセス修飾子)
### データに依存しない振る舞いを定義する (static)
### 派生を作る (継承)
## よく見るコード例
### 初期化のための静的メソッド
```php
<?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
<?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->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 __constructor($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 __constructor($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 __constructor($name) {
echo "吾輩は {$name} です";
$this->name = $name;
}
}
$user2 = new ParttimeUser("Bob");
// 吾輩は Bob です
$user2->setName("George");
```
スマホでボリュームを上げ下げしたいとき、ボタンを押させる。
中身に触れさせて、この端子をさわってね みたいなことはさせない。