RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

PHP オブジェクト指向 【再入門】

f:id:tech-rakus:20211217144942p:plain

はじめに

こんにちは、弊社サービス「配配メール」の開発に従事している id:soachr(そーく)といいます。
以前は id:north_mky というユーザで投稿していましたが結婚を期になんとなくユーザを変えました。
ID の由来はとくにありません。

今回は、駆け出しエンジニアさん向けに「オブジェクト指向」を PHP でプログラミングしようと思います。

対象読者

記事を読んでわかること

オブジェクト指向、正直よくわからんってなっていませんか?
オブジェクト指向プログラミング言語によらない概念の話なので抽象的でわかりづらいですよね。
今回、言語は PHP でわかりやすく説明していきたいと思います。

身近なあのシステムをオブジェクト指向で書いたら…?

なるべくイメージがつきやすい題材として今回は
今まさに見ている”ブログ”をオブジェクト指向でプログラミングしていくことにします。

なぜブログを題材にしたのか

BtoC, BtoB のサービスに問わず、システムによくある画面・機能があるからです。
すべて書きだすと記事の文字数がすごいことになるのと、かえって情報が多すぎてわかりづらくなるので割愛します。

よくあるシステムの画面・機能 ブログ
閲覧画面 記事の閲覧画面
閲覧画面/ユーザが操作できるボタンなど 記事に対する”いいね”
編集画面 記事の編集画面

ブログにはどんな要素があるか見てみましょう

ブログってどんな要素があるでしょうか。考えてみてください!

1. 記事

ぱっと思い浮かぶのは、ブログと言えば「記事」ですね。

  • 記事
    • タイトル
    • 本文
    • "いいね"するアイコン

2. できる操作

ではできる操作はなんでしょうか。色々ありますが、 よくある操作は以下ではないでしょうか。

  • 記事を作成する
  • 記事を公開する
  • 記事を閲覧する
    • 記事に”いいね”する

3. ユーザ

ここまでで見える部分や、よく行う操作についてはわかりました。
でも、目には見えないけれど暗黙的にある情報があります。
たとえば、「ユーザがログインをしているかどうか」です。

ブログは世界中の人がログインしなくても見られますし、一方でログインしていても見られます。
でも、両者では”できること”が違いますよね。
ログインしていない人は、”いいね”というアイコンを押したとしても、”ログインもしくは新規登録してください”と表示されることが多いと思います。

といったように、 そのシステムはだれが使うのかという情報があります。

  • ログインしているユーザ
  • ログインしていないユーザ(システムからみたら、閲覧画面を利用者に提供しているのでユーザですね)

ブログを構成する要素をみてきました。
では、これをPHPを使って、オブジェクト指向プログラミングをしていきましょう!

オブジェクト指向でこれらをプログラムに変換していきましょう

前章でみた情報を PHP を使い、オブジェクト指向で書くと以下のようになります。

記事クラス

<?php
class Article
{
    // 1. 記事
    private string $title; // タイトル
    private string $body; // 本文
    private array $likes = []; // "いいね"

    // 2. できる操作
    // 記事を作成する
    public function create():void
    {
        $this->title = "";
        $this->body = "";
    }

    // 記事を公開する
    public function publish(string $title, string $body) : void
    {
        $this->title = $title;
        $this->body = $body;
    }

    // "いいね"を追加する(ここはあとで説明します!)
    public function addLike(LoginUser $user) : void
    {
        array_push($this->likes, $user);
    }
}

まず、記事に関する情報とできる操作をclass Article{}でひとまとめにしました。
実際のシステムのイメージをそのまま落とし込んだようになって見やすいですね。

情報と操作が別々にあると、操作に必要な情報がどこにあるか探すのがとても大変になります。
実務だとプログラムの行数がかなり大きくなったり、プログラムファイル数が数十〜数百いったりすることはよくあります。

ですので、 意味のあるまとまり にすれば楽に理解できたり、修正できたりします。
(このことを「保守性が高い」といいます)

ユーザクラス

一方、ログインしている・していない人は以下のようにclass xxxxUser{}を書きました。
一見ややこしく見えますが、このあとで良さが効いてきます。
ポイントは以下の通りです!

  • ログインしているユーザ、していないユーザも記事に対して"いいね"する関数addLike()を持っている
  • ログインしているユーザは、プロパティに ID とユーザ名を持っている
    • 逆にログインしていないユーザは、ブログに登録していないので ID とユーザ名は持っていない
<?php
abstract class User
{
    abstract public function addLike(Article $article);
}

// ログインしているユーザ
class LoginUser extends User
{
    private int $id;
    private string $name;

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

    public function addLike(Article $article)
    {
        // 記事にいいねを登録する
        $article->addLike($this);
    }
}

// ログインしていないユーザ
class TempUser extends User
{
    public function __construct()
    {
        // ログインしていないので、IDやユーザ名を持ちません
    }

    public function addLike(Article $article)
    {
        // ログイン画面へ遷移する処理
    }
}

// "ユーザ"を作るだけのクラス
class UserFactory
{
    public static function create(?int $id, ?string $name = null): User
    {
        $user = null;

        if (is_null($id)) {
            // ログインしていないユーザを作る
            $user = new TempUser();
        } else {
            // ログインしているユーザを作る
            $user = new LoginUser($id, $name);
        }

        return $user;
    }
}

オブジェクト指向の良いところ

ここまでで、なんとなく良さそう・でも周りくどい書き方では、など色々思うところがあると思いますが、オブジェクト指向の良さを以下のキーワードを通して見ていきましょう。

1. カプセル化
2. 継承
3. ポリモーフィズム多態性

カプセル化

カプセル化クラスの内部情報や操作を外部から見せないこと を指します。 1
プログラム上ではアクセス修飾子が実現してくれます。
PHP ではそれぞれ以下の違いがあります。2

  • public
    • どのプログラムからも呼び出せる
  • protected
    • 定義したクラス自体、子クラス、親クラスであれば呼び出せる
  • private
    • 定義したクラス内でのみ呼び出せる

今までお見せしたプログラムではLoginUserクラスでアクセス修飾子を実は使い分けていました。
(コメントアウトの ①,②)

<?php
class LoginUser extends User
{
    private int $id; // ①
    private string $name; // ①

    public function __construct(int $id, string $name) // ②
    {
        $this->id = $id;
        $this->name = $name;
    }
    // 中略

LoginUserクラスは他のプログラムから呼び出そうとすると、publicをつけいてるコンストラクタとaddLike()関数しか呼び出せません。
プロパティ$id,$nameは呼び出せません。
直接$idを書き換えるシーンを想像してみてください。
ユーザを一意に特定する ID を書き換えるシーンは、ほとんどないと思います。

繰り返しになりますが、実務で扱うシステムは規模が大きいことが多いため、 内部仕様を完全に理解し記憶することはほとんどできません。
もしpublicで定義したプロパティを色々なプログラムで呼び出すと、 不具合が発生して修正するときに、呼び出されている箇所をすべて調査し、関連するすべての操作や画面の仕様を理解する必要があります
また修正した場合に、呼び出されている箇所ですべてに対して修正前と同じ挙動になっているかを確認しないと、もしかすると二次被害で別の不具合が発生するかもしれません。
度々不具合が起きてしまうと利用者への信頼を失ってしまいます。
そのため、なるべくクラスの外で呼び出せるプロパティ・関数は少なくする
→呼び出す側は内部情報を知らない
→つまりカプセル化したほうが、保守性が高い のです。

継承

継承はより簡単にいうと、”引き継ぐ”という意味です。
ブログはログインしているユーザ、そうでないユーザが存在しますが、ログインの有無にかかわらず「”いいね”をする」という操作が存在します。

このように、役割や立場は多少違うけれど共通の操作や情報を持つときは、継承を利用します。

前の章のプログラムを見返してみますと、User クラスのaddLike()関数がLoginUser,TempUserクラスに引き継がれています。
ポイントは「継承先クラスは継承元のアクセス修飾子がprivate以外のプロパティ・関数であれば、必ず引き継ぐこと」です。
必ずがキモで、逆を言えば継承先クラスは必ず継承元のプロパティ・関数を持っています。
逆に持っていなければ、エラーとなるような制約もあります。 3

これでなにが嬉しくなるかですが、
結論からいうと開発や運用(利用者が不便なく使えるようにシステムを動かし続けること)をする人がシステムを理解し、どうプログラムを修正すればよいかがわかりやすくなります。

実業務ではほとんどの場合は、 1 つのシステムを複数人で開発したり運用したりします。
人によってそのシステムに対する仕様の理解度はまちまちです。

ですが、残念なことに開発には納期が決まっているのです。
限られた時間の中で、より早く必要な分システムを理解しなければなりません。
そのときに継承が使用されていれば、以下のように早くシステムを理解できます。

  • 継承元クラスの役割を知ると、継承先クラスの役割がだいたい把握できる
    • 継承先クラスの理解は、継承元との差分だけを確認すればよい
    • 追加で継承先クラスを作成するときに、必要な関数を考える手間や書く手間を省ける

ポリモーフィズム多態性

ポリモーフィズムは今まで書いてきた クラスを利用する処理(=業務ロジックといいます) で良さがわかります。

<?php
/***** 業務ロジック *****/
// 記事の投稿
$article = new Article();
$article->create();
$article->publish("ブログのタイトル", "ブログの本文");

// 中略

// "いいね"をする
$user =  UserFactory::create($id, $name); // ログインしている場合はID,ユーザ名が渡される
$user->addLike($article);

"いいね"をする処理をみてください。

ポイントはnewをする処理がないことです。
引数にキーとなる情報(今回で言えば$id)をUserFactory::create()という関数に渡しているだけです。
業務ロジックは、具体的にどのクラスを new する必要があるのか考える必要がなくなります。

次には 2 行目addLike()ですが、ログインしているかどうかで実際に実行される処理は異なります
ユーザはログインしていれば"いいね"ができ、ログインしていなければログイン画面へ遷移する処理が実行されます。

ではなぜポリモーフィズムが実現できているかですが、それには継承が関わっています。4

もう一度ユーザに関連するプログラムを見てみましょう。
コメントアウトしている ①②③ を順に見てください。
① でUserクラスを継承すると、②で定義されたUserクラスの関数を、③で$userどのクラスかを意識することなく使用することができます

<?php
abstract class User
{
    abstract public function addLike(Article $article); // ②
}

// ログインしているユーザ
class LoginUser extends User // ①
{
    private int $id;
    private string $name;

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

    public function addLike(Article $article)
    {
        // 記事にいいねを登録する
        $article->addLike($this);
    }
}

// 中略

// "いいね"をする
$user =  UserFactory::create($id, $name);
$user->addLike($article); // ③

この「やること(いいねをするという操作)」は決まっているけれど実際にどのように「処理」が実行されるかは隠蔽されている (業務ロジックで知る必要がない)ことをポリモーフィズム多態性)といいます。

おわりに

今回の例のようにすべて一人で書く場合は、プログラムの全容を理解しているので旨味は分かりづらいですが、実務では存在するすべてのプログラムを理解する機会はほぼないです。(時間がいくらあっても足りません)
それなのに、そのシステムを修正するには修正する箇所のプログラムを理解する必要があります。
本内容からオブジェクト指向を使うことで、"楽"にシステムを開発・運用することができるイメージを持てれば嬉しいです。


  • エンジニア中途採用サイト
    ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
    ご興味ありましたら是非ご確認をお願いします。
    20210916153018
    https://career-recruit.rakus.co.jp/career_engineer/

  • カジュアル面談お申込みフォーム
    どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
    以下フォームよりお申込みください。
    rakus.hubspotpagebuilder.com

  • イベント情報
    会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
    rakus.connpass.com


  1. カプセル化は人によって色々な意味合いを持つため今回はフォーカスを絞りました

  2. 最近のプログラミング言語にはだいたいあると思いますが、言語ごとに仕様が異なります。筆者はJavaを先に学んでいたのでPHPとの言語仕様の違いに戸惑いました。

  3. この表現は正確ではありません。理解のしやすさのために予約語abstructに触れていません。

  4. この表現は正確ではありません。理解のしやすさのためにこのように表記しています。より厳密に言えばポリモーフィズムという概念は継承という概念を含む概念です。

Copyright © RAKUS Co., Ltd. All rights reserved.