プログラミングを覚えるにつれ、
目にする機会が増えてくるのが、クラスやオブジェクト指向というキーワード。
これまでに何度もなんどもググっては挫折、ググっては挫折を繰り返してきたのですが、
何度も何度も 『Laravel』のソースコードを読み進めるうちに、
なんとなく、ぼんやりですが、使い分けがわかりはじめた(ような気がする)ので、
忘れないように記事にしようと思います。
※あくまでも初心者向けで、いろいろはしょっていますので、ぜひ寛大な心でお読みいただければと思います。
PHP クラスの前にまずは関数(機能)
PHPクラスの説明の前にどうしても説明が必要なのが、『関数』という仕組み。
関数という言葉がややこしい雰囲気をかもしだしていますが、
function(ファンクション) という英語を訳すと、関数の他に、
機能、働き、作用 といった意味もあるようです。
『関数(機能)』は、
PHPが最初から用意しているものもありますが、
自分で自由に名前をつけてつくる事もできます。
今回は、
2つの数字を与えると足し算して、
その結果を教えてくれる『関数(機能)』を作ってみます。
<?php function add($v1, $v2) { $answer = $v1 + $v2; return $answer; } $v3 = add(1,2); ?>
中身の解説ははしょりますが要点は、
- function というキーワードで始まる
- functionの後ろに ()をつける。
- ()の中に値(引数)が入る場合がある
- 引数の後に {} で囲んで処理を書く
- {} の最後に return で処理した結果を返す(返さない場合もあり)
という感じです。
『関数』の関連記事
PHP クラスの中では関数が『メソッド』に変わるフシギ
本題の『クラス』ですが、
『クラス』というのはざっくりいうと、
『関数』と『変数(箱)(定数)』をまとめたものです。
クラスなし | 関数 | 値(変数など) |
クラスの中 | メソッド | プロパティ |
って思いますよね普通。僕もそうでした。
『関数』なら『関数』のままでえーやんと。
『method(メソッド)』という単語を調べると、方法とか、順序という意味のようです。
『関数(機能)』 の意味と比べると、
と思ったりしますが、
と何度突っ込んだことか。
けどまぁ慣れるしかないです。
大事なことなのでもう一度。
『function』で始まる『関数(機能)』は、
クラスの中に入ると『メソッド』という名前に変わるんです。
『変数(箱)』は、
クラスの中では(なぜか)『プロパティ』という名前に変わります。
クラスなし | 関数 | 値(変数など) |
クラスの中 | メソッド | プロパティ |
で、ここからさらにこんがらがるんですが、先に図解をご覧ください。
PHP クラスと抽象クラスとインターフェースとトレイトを図にしてみた
だいぶ整理したつもりなんですが、それでもややこしい。。
一つずつ解説するとかなりの分量になるので、シンプルさ優先でまとめてみました。
クラス (具象クラス) オブジェクト指向のメイン
- classではじまる
- クラスはコンストラクタ(初期化)、メソッド(関数)、プロパティ(変数などの値)などを持てる
- function(関数)の前に、public をつけると外から使える状態になる (他にstaticなどあり)
- クラスはnewを使うことでインスタンス(実際に使える状態)にできる
- PHPのクラスは複数同時に継承はできない(単一継承)
サンプルコード。
<?php //親クラス class ParentClass{ public function Item1($str){ // functionがついているのでメソッド(関数) echo $str.'親クラスです。 '; } public function Item2(){ // functionがついているのでメソッド(関数) echo 'Item2は親クラスで動いてます'; } } //インスタンスを生成 $parent = new ParentClass(); //メソッドの呼出し $parent->Item1('これは'); $parent->Item2(); ?>
結果
これは親クラスです。
Item2は親クラスで動いています。
※返り値はreturn で返すことが多いですが、シンプルにするため echo で文章表示だけにしています。
クラスの継承 (extends)
- class クラス名 の後に extends をつけて、継承させたいクラス名を書く
- 継承したクラスと メソッド(関数)の名前が重なっていたら、子クラス側を優先する(上書き(オーバーライド))
サンプルコード。
<?php //親クラス class ParentClass{ public function Item1($str){ echo $str.'親クラスです。 '; } public function Item2(){ echo 'Item2は親クラスで動いてます'; } } //子クラス extends をつけて継承させている class ChildClass extends ParentClass{ public function Item1($str){ echo $str.'子クラスです。'; } } //インスタンスを生成 $parent = new ParentClass(); $child = new ChildClass(); //メソッドの呼出し $parent->Item1('これは'); $child->Item1('これは'); $parent->Item2(); $child->Item2(); ?>
結果
これは親クラスです。
これは子クラスです。
これは親クラスで動いています
これは親クラスで動いています
Item1 メソッド(関数)が 親クラスでも、子クラスでも設定されているので、子クラスの方を優先してるということですね。
抽象クラス (abstract) 不要論もあったりします
- 抽象クラスは継承前提のクラス。インスタンス化(new)はできない
- 継承クラスは abstract で始める
- functionの前にabstractをつけると、継承されたクラスで必ず指定する必要がある(この場合はfunctionの中身は書かない)
- abstractなしの関数も書ける (こちらは継承クラスで使わなくても問題なし)
サンプルコード
<?php //抽象クラス 頭に abstractをつけて class指定 abstract class SenzoClass{ //頭にabstractをつけてメソッド(関数)を指定。この場合は中身をかいたらNG abstract public function Item1(); public function Item2($str){ echo $str.'抽象クラスです。'; } } //親クラス extendsをつけて継承している class ParentClass extends SenzoClass{ public function Item1(){ echo 'Item1は親クラスで動いてます'; } } //インスタンスを生成 $parent = new ParentClass(); //メソッドの呼出し $parent->Item1(); $parent->Item2('Item2は'); ?>
抽象クラスでは、
- abstractをつけると使えるメソッドを絞ることができる反面、
- abstractをつけないとインスタンス化ができないだけで、
ほとんど普通のクラス(具象クラス)と同じような性質なのかなと思います。
という意見も実際にあるようです。(後述のポッドキャスト(PHPの現場)参照)
インターフェース (interface) 2者間の接点
インターフェースの特徴をずらり。
1.インターフェースにはメソッド名しかかけない
2. インターフェースもインスタンス化できない
3. 抽象クラスにセットする場合は1つのみ
4. クラスにセットする場合は複数セットできる
5. セットする場合はimplements と書く
6. インターフェースをセットすると、インターフェースで記載されたメソッドと一致必須
抽象クラスでは、
- abstractとつけた関数(メソッド)と、
- abstractをつけない関数(メソッド)が
共存できちゃうのですが、
インターフェースは abstractとつけた関数(メソッド)しか使えないような形です。
<?php //インターフェース 頭にinterfaceとつける interface Makimono{ public function Makimono1(); } //抽象クラス インターフェースを読み込ませるには implements と書く abstract class SenzoClass implements Makimono{ abstract public function Makimono1(); abstract public function Item1(); public function Item2($str){ echo $str.'抽象クラスです。'; } } //親クラス extendsをつけて継承している class ParentClass extends SenzoClass{ public function Makimono1(){ echo '代々伝わる巻物です'; } public function Item1(){ echo 'Item1は親クラスで動いてます'; } } //インスタンスを生成 $parent = new ParentClass(); //メソッドの呼出し $parent->Item1(); $parent->Item2('Item2は'); $parent->Makimono1();
トレイト (trait) クラスじゃないけどメソッドを引き継げる
次は『トレイト』ですよ。
英語を訳すと、
『特性』とか『特徴』という意味があるようで、
継承じゃないけど継承みたいに使えるシロモノです。
と最初は思いました。
よくよく調べてみると『トレイト』という機能は、
『PHP5.4』(2012年3月1日にリリース)から使えるようになっています。
『PHP5.3』から追加された『名前空間(namespace)』という機能を活用しています。
『名前空間(namespace)』・・namespace と記載しておくと、フォルダを分けるようにクラスや関数の影響範囲を制限できる
->もし使わないと、同じクラス名があった時にごっちゃになってしまう
もし、違う『名前空間(namespace)』のクラスを使いたいときは、
『use なんちゃら(クラス名や名前空間)』という文を使うとOKです。
と思ったりしてましたが、これまた慣れるしかないですね・・
そんな、
- 『名前空間(namespace』や
- 『use』
の機能を使って使えるようになったのが『トレイト』。
もともとPHPでは、
一度に1つのクラスしか継承できない(単一継承)というルールがあったので、
と思ったのかどうかわかりませんが、
クラスではないけれど、関数(メソッド)を引き継げるという機能が生まれたんですね。
『トレイト』の特徴としては以下。
- トレイトもインスタンス化できない
- 継承ではなくクラス内に 『use トレイト名』で使えるようになる
- use なんちゃら と書く事でいくつでもひっぱってこれる
テストコード
<?php //頭にtrait と書く trait Ninja{ public function Shuriken(){ echo 'にんじゃりばんばん'; } } trait Samurai{ public function Katana(){ echo 'るろ剣 続編楽しみでござる'; } } //クラスの中でuseとつけるとひっぱってこれる class ParentClass { use Ninja; //trait名を書く use Samurai; //こっちもtrait名 public function Item1(){ echo 'Item1は親クラスで動いてます'; } } //インスタンスを生成 $parent = new ParentClass(); //メソッドの呼出し $parent->Item1(); $parent->Shuriken(); $parent->Katana();
Item1は親クラスで動いてます
にんじゃりばんばん
るろ剣 続編楽しみでござる
useと書くことでいくつでもメソッド(関数)を持ってこれるというのが、
『トレイト』の特徴になります。
最近人気の『Laravel』にも、
この『namespace(名前空間』とか『use なんちゃら』というコードが至るところで使われています。
DI (依存性の注入) (Laravelのケース)
最後に『Laravel』の特徴といわれている、『DI』。
英語だと Dependency Injection という呼び名で、だいぶ分かりにくいんですが、
ようは、
クラスの中の関数(メソッド)にインスタンスをつっこむという方法をさします。
もうちょっと細かく書くと、
クラスの中の関数(メソッド)かコンストラクタ(初期化)に、
・インスタンスか、
・インターフェースか、
・抽象クラスを
引数に渡すことです。
メソッド(関数)に渡すか、コンストラクタ(関数)に渡すかで呼び名も変わるようです。
- メソッド(関数)に設定・・メソッドインジェクション
- コンストラクタ(初期化)に設定・・コンストラクタインジェクション
(他にセッターインジェクションというものもあり)
『DI』の特徴
- クラス内のコンストラクタ(初期化)やメソッド(関数)の引数に設定する
- 設定する引数はインスタンス、インターフェース、抽象クラスのいずれか
- クラスの中でインスタンス化しないのでテストしやすくなる
ということです。
テストコード
<?php class MailSender { public function send($to, $message) { //ここに処理を書く } } class UserService { //MailSenderクラスのインスタンスを引数にする public function notice(MailSender $mailsender, $to, $message) { $mailsender->send($to,$message); } }
インスタンス化させるには普通は『new クラス名』と書く必要があるんですが、
『DI』の場合だと、
インスタンス化しなくてもインスタンスしたかのように、
他クラスのメソッドが扱えるというのが特徴です。
しっかりテストコードなどを書くような規模になると便利さを体感できるようです。
抽象クラス(abstract)とインターフェースとトレイトとDIの使い分け
これらをまとめていて、頭を悩ませていたのが、
という事。
そんな時、たまたま見つけたPodcastで、
3名の現役プログラマーの方の意見交換がされていて、聞き入ってみました。
内容をざっとまとめると、
- インターフェースは使えるメソッドを指定できる分使いやすい。
- トレイトはプロパティも使えるけどほぼメソッドのみ。
- 抽象クラスは使う派、使わない派に分かれる
- DI(依存性の注入)は、小規模では無くてもいい、中規模以上になると必要かも。
三者三様、ケースバイケース、現場による、ということで、がっちりとルールが決まっているわけではないようです。
としんみりしながら聞き入っていました。
最後に
小規模のプログラミングの場合はあまり出番がないかもですが、
中規模以上、またはチーム開発ともなれば、
チームにあわせた設計方法が必要になってきますし、
他の人がつくったコードを読み、その内容を把握しつつ、追加機能をつくったりする事になるので、
クラス設計やそれぞれの機能の使い分けというのも少しずつ把握していかないとですね。
参考資料
『PHP(Laravel)』ではこんな記事も読まれています。
1. 【PHP/Laravel】初心者向けの動画をリリースしました【Udemy】2. 【Laravel(PHP)】初心者向け アプリのつくり方 をリリースしました【techpit】
3. 【PHP】【Laravel】CSVエクスポートの方法〜5つのポイント〜
4. 【PHP】オブジェクトと連想配列の違いについて調べてみた【初心者向け】
5. 【PHP】CSVインポートの方法〜大量データもバルクインサートでバッチリ!〜【laravel】
6. 【PHP】【図解】クラスと抽象クラスとインターフェースとトレイトとDIをまとめてみた【初心者向け】
7. 【PHP】配列や連想配列が覚えづらかったので学校に例えてみた【初心者向け】
8. 【PHP】ホームページに天気予報を表示させる方法【Webスクレイピング】【初心者向け】
9. 【Laravel】Webアプリ環境構築の仕方【Vue.js】【初心者向け】
10. 【PHP】2次元(多次元)配列でデータ取得したいならarray_columnがめっさ便利
アオキのツイッターアカウント。
この記事へのコメントはありません。