PHP7でデザインパターン入門14/23 Chain of Responsibilityパターン

Chain of Responsibility パターンとは

Chain of Responsibility パターン - Wikipedia

一つの コマンドオブジェクトと一連の 処理オブジェクトから構成される。各処理オブジェクトは、処理できるコマンドオブジェクトの種類と、自身が処理できないコマンドオブジェクトをチェーン内の次の処理オブジェクトに渡す方法を記述する情報を保持する。また、新たな処理オブジェクトをチェーンの最後に追加する機構を備える

Chain of Responsibiliy(責任の連鎖) とは 責任の「たらい回し」であるとも言える。 このパターンを使うと要求する側と処理する側の結びつきを弱めることができ、それぞれを部品として独立させることができる。 また状況によって要求を処理するオブジェクトが変化するようなプログラムにも対応することができる。

要求が来る。その人が処理できるなら対応、できないなら次の人へたらい回しにする…処理に対応できるまでこれを繰り返す。これがChain of Responsibilityパターンである。

example

発生したトラブルを誰かが処理しなければならない状況を考える。

以下サンプルコード。

github.com

Troubleクラス

発生したトラブルを表現するクラス。numberはトラブル。getNumberでトラブル番号を得る。

Supportクラス

トラブルを解決する連鎖を作るための抽象クラス。 nextプロパティはたらい回しする先を指す。resolveメソッドはサブクラスで実装することを想定した抽象メソッド。supportメソッドはresolveメソッドを呼び出し戻り値がfalseなら次の人にたらい回しにする。

NoSupportクラス

Supportクラスのサブクラス。NoSupportクラスのresolveメソッドは常にfalseを返す。「自分は何も処理しない」というクラス

LimitSupportクラス

limitで指定した番号未満のトラブルを解決するクラス。

OddSupportクラス

奇数番号のトラブルを処理するクラス。

SpecialSupportクラス

指定した番号のトラブルに限って処理するクラス

Main

サンプルコード実行用

実行結果

$ php Main.php
[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

役割

Handler

Handler役は要求を処理するインターフェースを定める役。サンプルコードにおけるSuportクラス。

ConcreteHAndler

要求を処理する具体的な役。サンプルコードにおけるNoSupport, LimitSupport, OddSupport, SpecialSupportクラス。

Client

サンプルコードにおけるMainクラス。

まとめ

  • 要求を出す人と要求する人をゆるやかに結びつける
  • 動的に連鎖の形態を変える
  • たらい回し処理は遅くならないのか?
    • 要求する処理を誰が行うか決まっている場合に比べて遅くなる。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

多重配列内の特定のキーを検索しその値を取り出す

下のような多重配列があるとする。

<?php
$a = [
    'foo' => [
        'bar' => [
            'user_id' => 10
        ]
    ]
];

この配列構造を知らない状態で、'user_id'キーの値だけ検索して取り出したい。

再帰処理を書けばいいだけなんだけど、PHPの標準ライブラリで実現できないかと思って検索すると、できそうなクラスが見つかった。

PHP: RecursiveIteratorIterator - Manual

再帰的なイテレータの反復処理に使用します。

これでできそう。しかし、RecursiveIteratorIterator… すごいクラス名だ。

サンプルコードを参考にして、以下のようなコードを作成した。 これで、どのような多重配列構造の配列でも特定のキー名から対応する値を取り出せる。

<?php
function searchValueByKey(array $ary, $str) {
    $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($ary));
    foreach ($it as $k => $v) {
        if ($str === $k) { return $v; }
    }
    return false;
}

$a = [
    'foo' => [
        'bar' => [
            'user_id' => 10
        ]
    ]
];

// search 'user_id' value in the array
echo searchValueByKey($a, 'user_id'); // 10

本記事はQiita に投稿しました。

[PHP]多重配列内の特定のキーを検索しその値を取り出す on @Qiita http://qiita.com/tic40/items/2e45e2a9d25b91eed8f1

PHP7でデザインパターン入門13/23 Visitorパターン

Visitor パターンとは

アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。

Visitor パターン - Wikipedia

データ構造の中にたくさんの要素が格納されており、各要素に対して何かの処理をする必要があるとする。このときのその処理のコードはどこへ書くべきか。データ構造を表しているクラスへ処理を書いた場合、もし処理が1種類と限らなければ、新しい処理が必要になるたびにデータ構造を修正することになる。Visitorパターンではデータ構造と処理を分離する。

example

サンプルコード

訪問者が渡り歩くデータ構造として、Compositeパターン*1で登場したファイルとディレクトリで構成されたデータ構造の中を訪問者が渡り歩きファイルの一覧を表示するプログラムを作成する。

github.com

Visitor

ファイルやディレクトリを訪れる訪問者を表す抽象クラス

Element

Visitorクラスのインスタンスを受け入れるデータ構造を表すクラス

ListVisitor

Visitorクラスのサブクラスで、ファイルやディレクトリの一覧を表示するクラス

Entry

FileとDirectoryのスーパークラスとなる抽象クラス

File

ファイルを表すクラス

MyDirectory

ディレクトリを表すクラス

Main

動作テスト用

実行結果

$ php Main.php
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

役割

Visitor役

データ構造の具体的な要素ごとに訪問する visit()メソッドを宣言する。サンプルコードにおけるVisitorクラス。

ConcreteVisitor役

Visitor役のインターフェースを定義する。visitXXX()メソッドを実装し、ここのConcreteElement役ごとの処理を記述する。 サンプルコードにおけるListVisitorクラス。

Element役

Visitor役の訪問先を表す役。訪問者を受け入れるaccept()メソッドを宣言する。acceptメソッドにはVisitor役が渡される。 サンプルコードにおけるElementインターフェース。

ConcreteElement役

Element役のインターフェースを実装する役。サンプルコードにおけるFile, MyDirectoryクラス。

ObjectStructure役

オブジェクト構造。Element役の集合を扱う役。ConcreteVisitor役が個々のElement役を扱えるようなメソッドを備えている。 サンプルコードにおけるMyDiretoryクラス(MyDirectoryは1人2役)。

まとめ

ダブルディパッチ

elementはvisitorをacceptし、visitorはelementをvisitしている。VisitorパターンではConcreteElement役とConcreteVisitor役の組によって実際の処理が決定する、これを一般にダブルディパッチと呼ぶ。

Open-Closed Principle

OCP。

  • 拡張(Extension)については開かれている
  • 修正(Modification)については閉じられている

既存のクラスを修正せずに拡張できるようにすべき、という方針。 デザインパターンの目的はこのような仕組みを提供することにある。

ConcreteVisitor役の追加は容易

新しいConcreteVisitor役を追加する際に、ConcreteElement役を修正する必要はない

ConcreteElement役の追加は困難

サンプルコードで、EntryクラスのサブクラスとしてDeviceクラスを追加したいとする。DeviceクラスはFileクラスとMyDirectory区rスの兄弟にあたる。そのとき、Viistorクラスには新しいvisitDevice()メソッドを追加する必要が生じる。そしてvisitorクラスのサブクラス全てに新たにvisitDevice()メソッドを実装しなければいけなくなる。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

SOFT SKILLS を読んだ

Soft Skills: The Software Developer's Life Manual

Soft Skills: The Software Developer's Life Manual

この本は結構前に購入していて*1、先日やっと読み終えた。とにかく分厚い洋書で、洋書自体読む習慣のない自分にはかなり時間がかかった。

読書は楽しい時間なんだけど、洋書(英語)を読む際にはある程度気合が入ってる状態じゃないとできない。たまに分からない表現があってもやもやしたりたまに辞書を引いたり、自分にとって洋書を読むのは、得られるメリット以上にまだ効率が悪いかなという感じ。

あ、本自体は面白かったです。今は翻訳版も出てるね。

*1:amazonの購入履歴を確認したら2015/11/13 に購入していたようだ

API認証フロー Resource Owner Password Credentials Grantを自社モバイルアプリで利用する

目的

  • 自社アプリのような信頼できるモバイルアプリケーションからAPIを利用したい
  • モバイルアプリからAPIを利用する際のセキュリティ脆弱性は必ず排除する必要がある。

前提

  • APIは既に存在し自社webアプリケーションから利用されている。webアプリからの認証には client credentials認証フローを採用している。
  • REST API
  • APIリクエスト時には、アクセストークンを必須としている。アクセストークン未発行の場合アクセストークンの発行を行う必要がある。
  • 第3者へのAPI公開はまだ考えていないため、サードパーティーアプリからの認証は考えなくて良い。
  • なるべく時間はかけたくない

やったこと

OAuth 認証フローについて調べてみると、RFC文書内に認証フローは4種類定義されていおり、その中の Resource Owner Password Credentials GrantAPI認証フローとして今回の要件をみたす形で利用できる判断し実装した。

Resource_Owner_Password_Credentials_Flow

     +----------+
     | Resource |
     |  Owner   |
     |          |
     +----------+
          v
          |    Resource Owner
         (A) Password Credentials
          |
          v
     +---------+                                  +---------------+
     |         |>--(B)---- Resource Owner ------->|               |
     |         |         Password Credentials     | Authorization |
     | Client  |                                  |     Server    |
     |         |<--(C)---- Access Token ---------<|               |
     |         |    (w/ Optional Refresh Token)   |               |
     +---------+                                  +---------------+

            Figure 5: Resource Owner Password Credentials Flow


   The flow illustrated in Figure 5 includes the following steps:

   (A)  The resource owner provides the client with its username and
        password.

   (B)  The client requests an access token from the authorization
        server's token endpoint by including the credentials received
        from the resource owner.  When making the request, the client
        authenticates with the authorization server.

   (C)  The authorization server authenticates the client and validates
        the resource owner credentials, and if valid, issues an access
        token.

RFC 6749 - The OAuth 2.0 Authorization Framework

この認証フローでは、トークン発行時のリクエストに、username, password を指定する。そのusername, password の組み合わせが有効だった場合、有効なリクエストだと判断しアクセストークンを発行する。

実際のフロー

  • モバイルアプリのログインフォームにユーザがusername(ID), passwordを入力する。このusernameとpasswordはアプリケーションのユーザログイン時に使うものと同じ。
  • それをもとにモバイルアプリがAPIトークン発行リクエストをAPIへ送信する。
  • APIでusername, password の組み合わせをDBと照合し、正しければアクセストークンを発行する。
  • 以後そのアクセストークンでモバイルアプリはAPIリクエストを送信する。

ユーザによるリソースへのアクセス制限

モバイルアプリからリクエストパラメータを改ざんして送信されることは起こり得る。 例えば、ユーザ情報変更APIをモバイルアプリから利用するとする。ユーザIDによるリソースのアクセス制限を設定してない場合のリクエストパラメータを他人のIDに書き換えてリクエストを送信されると他人のリソースが変更できてしまうという重大な脆弱性になる。

この問題を回避するために、

  • アクセストークン発行時に ユーザIDを発行したアクセストークンと紐付けて保存する
  • 個人情報を扱うAPIリソースへのアクセス時には、そのアクセストークンを発行したユーザIDとAPIで参照しようとしているユーザIDが一致しているかチェックする。一致している場合はアクセスを許可、一致していない場合はアクセスを拒否しエラーを返す。

まとめ

この認証フローはモバイルアプリケーションがユーザのアプリケーションへのログインID, passwordを知る必要がある。つまりログイン情報を知っても問題ないアプリケーションから利用する場合に限られる。なので自社製アプリなど信頼できるアプリケーションにこの認証フローは使えない。

認証フローに限らずAPI設計を考える上で、Web API: The Good Parts にはかなりお世話になった。良書。

Web API: The Good Parts

Web API: The Good Parts

利用したライブラリとか

OAuth2 サーバライブラリ(PHP)を利用

GitHub - bshaffer/oauth2-server-php: A library for implementing an OAuth2 Server in php

PHP7でデザインパターン入門12/23 Decoratorパターン

Decorator パターンとは

このパターンは、既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする。

Decorator パターン - Wikipedia

スポンジケーキが1つあるとする。クリームを塗れば何も載っていないショートケーキができあがる。いちごを並べれば、ストロベリーショートケーキができあがる。いずれにおいてもベースとなるのはスポンジケーキである。様々なデコレーションを施すことで、様々な目的にあったケーキとなる。

オブジェクトもこのような性質がある。まず中心となるベースオブジェクトがある。それに飾り付けとなる機能を一皮一皮かぶせていくことで、より目的にあったオブジェクトに仕上げていく。このようなオブジェクトにデコレーションを施していくようなデザインパターンをDecoratorパターンと呼ぶ。

example

文字列の周りへ飾り枠をつけて表示するサンプルコード。

Display: 文字列表示用抽象クラス

<?php
abstract class Display
{
    abstract public function getColumns(): int;
    abstract public function getRows(): int;
    abstract public function getRowText(int $row);

    public function show()
    {
        for ($i = 0; $i < $this->getRows(); $i++) {
            echo $this->getRowText($i) . PHP_EOL;
        }
    }
}

StringDisplay: 1行だけからなる文字列表示用クラス

<?php
class StringDisplay extends Display
{
    private $string;

    public function __construct(string $string)
    {
        $this->string = $string;
    }

    public function getColumns(): int
    {
        return strlen($this->string);
    }

    public function getRows(): int
    {
        return 1;
    }

    public function getRowText(int $row)
    {
        if ($row === 0) {
            return $this->string;
        }

        return null;
    }
}
~

Border: 「飾り枠」を表す抽象クラス

<?php
abstract class Border extends Display
{
    protected $display;

    protected function __construct(Display $display)
    {
        $this->display = $display;
    }
}

SideBorder: 左右にのみ飾り枠をつけるクラス

<?php
class SideBorder extends Border
{
    private $borderChar;

    public function __construct(Display $display, string $ch)
    {
        parent::__construct($display);
        $this->borderChar = $ch;
    }

    public function getColumns(): int
    {
        return 1 + $this->display->getColumns() + 1;
    }

    public function getRows(): int
    {
        return $this->display->getRows();
    }

    public function getRowText(int $row): string
    {
        return $this->borderChar . $this->display->getRowText($row) . $this->borderChar;
    }
}

FulBorder: 上下左右に飾り枠をつけるクラス

<?php
class FullBorder extends Border
{
    public function __construct(Display $display)
    {
        parent::__construct($display);
    }

    public function getColumns(): int
    {
        return 1 + $this->display->getColumns() + 1;
    }

    public function getRows(): int
    {
        return 1 + $this->display->getRows() + 1;
    }

    public function getRowText(int $row): string
    {
        if ($row === 0) {
            return "+" . $this->makeLine('-', $this->display->getColumns()) . "+";
        } elseif ($row === $this->display->getRows() + 1) {
            return "+" . $this->makeLine('-', $this->display->getColumns()) . "+";
        }

        return "|" . $this->display->getRowText($row - 1) . "|";
    }

    private function makeLine(string $ch, int $count): string
    {
        $buf = "";
        for ($i = 0; $i < $count; $i++) {
            $buf.= $ch;
        }

        return $buf;
    }

Main

<?php
$b1 = new StringDisplay("Hello, world.");
$b2 = new SideBorder($b1, '#');
$b3 = new FullBorder($b2);
$b1->show();
$b2->show();
$b3->show();
$b4 = new SideBorder(
    new FullBorder(
        new FullBorder(
            new SideBorder(
                new FullBorder(
                    new StringDisplay("こんにちは。")
                ),
               '*'
            )
        )
    ),
    '/'
);
$b4->show();

実行結果

$ php Main.php
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-------------------+/
/|+-----------------+|/
/||*+-------------+*||/
/||*|Hello, world!|*||/
/||*+-------------+*||/
/|+-----------------+|/
/+-------------------+/

ソースコード

github.com

役割

Component

機能を追加するときの核となる役。デコレーションする前のスポンジケーキ(ベース)部分にあたる。インターフェースだけを定める。サンプルコードにおけるDisplayクラス。

ConcreteComponent

具体的な実装をしているスポンジケーキ。サンプルコードにおけるStringDisplayクラス。

Decorator

Component役と同じインターフェースを持つ。さらに、Decorator役が飾る対象となるComponentを持つ。この役は、自分が飾っている対象を知っている。サンプルコードにおけるBorderクラス。

ConcreteDecorator

具体的なDecoratorの役。サンプルコードにおけるSideBorder, FullBorderクラス。

まとめ

透過的インターフェース

  • Decoratorパターンでは飾り枠と中身を同一視している。サンプルコードでは、飾り枠を表すBorderクラスが、中身を表すDisplayクラスのサブクラスになっているところで、その同一視が表現されている。
  • 飾り枠を使って中身を包んでも、インターフェースは「透過的」(隠れていない状態)であるため、飾り枠をたくさん使って包んでも、インターフェースは全く変更されない。

包まれるもの(中身)を変えずに機能追加ができる

Decoratorパターンでは、委譲が使われている。飾り枠に対してやってきた要求はその中身にたらい回し(委譲)される。

動的な機能追加ができる

委譲でクラス間をゆるやかに結合しているため。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

PHP7でデザインパターン入門11/23 Compositeパターン

Composite パターンとは

「構造に関するパターン」に属する。Composite パターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。

Composite パターン - Wikipedia

コンピュータにおけるディレクトリのように、ファイルとディレクトリをまとめて扱うような場合、容器(ディレクトリ)と中身(ファイル)を同じ種類として扱うと便利な場合がある。容器の中には、中身を入れてもさらに新たな容器を入れても良く、このように入れ子になった構造、再帰的な構造を作る事ができる。

Comosite パターンとは上記のような容器と中身を同一視し、再帰的な構造を作るデザインパターンである。

example

サンプルプログラムはファイルとディレクトリを模式的に表現したもの。

  • File: ファイルを表す
  • MyDirectory: ディレクトリを表す(PHPではDirectoryクラスは標準で定義済みのためMyDirectoryとした)
  • Entry: FileとMyDirectoryの両者をとりまとめるスーパークラス

Entry 抽象クラス

<?php
abstract class Entry
{
    abstract public function getName(): string;
    abstract public function getSize(): int;

    public function add(Entry $entry)
    {
        throw new RuntimeException();
    }

    public function printList()
    {
        $this->printListWithPrefix("");
    }

    abstract protected function printListWithPrefix(string $prefix);

    public function __toString(): string
    {
        return $this->getName() . " (" . $this->getSize() . ")";
    }
}

MyDirectory クラス

<?php
class MyDirectory extends Entry
{
    private $name;
    private $directory = [];

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

    public function getName(): string
    {
        return $this->name;
    }

    public function getSize(): int
    {
        $size = 0;
        foreach ($this->directory as $v) {
            $size += $v->getSize();
        }
        return $size;
    }

    public function add(Entry $entry): Entry
    {
        $this->directory[] = $entry;
        return $this;
    }

    protected function printListWithPrefix(string $prefix)
    {
        echo $prefix . "/" . $this . PHP_EOL;
        foreach ($this->directory as $v) {
            $v->printListWithPrefix($prefix . "/" . $this->name);
        }
    }
}

File クラス

<?php

class File extends Entry
{
    private $name;
    private $size;

    public function __construct(string $name, int $size)
    {
        $this->name = $name;
        $this->size = $size;
    }
    public function getName(): string
    {
        return $this->name;
    }
    public function getSize(): int
    {
        return $this->size;
    }
    protected function printListWithPrefix(string $prefix)
    {
        echo $prefix . "/" . $this . PHP_EOL;
    }
}

Main

<?php
try {
    echo "Making root entries..." . PHP_EOL;
    $rootdir = new MyDirectory("root");
    $bindir = new MyDirectory("bin");
    $tmpdir = new MyDirectory("tmp");
    $usrdir = new MyDirectory("usr");
    $rootdir->add($bindir);
    $rootdir->add($tmpdir);
    $rootdir->add($usrdir);
    $bindir->add(new File("vi", 10000));
    $bindir->add(new File("latex", 20000));
    $rootdir->printList();

    echo "" . PHP_EOL;
    echo "Making user entries..." . PHP_EOL;
    $yuki = new MyDirectory("yuki");
    $hanako = new MyDirectory("hanako");
    $tomura = new MyDirectory("tomura");
    $usrdir->add($yuki);
    $usrdir->add($hanako);
    $usrdir->add($tomura);
    $yuki->add(new File("diary.html", 100));
    $yuki->add(new File("Composite.java", 200));
    $hanako->add(new File("memo.tex", 300));
    $tomura->add(new File("game.doc", 400));
    $tomura->add(new File("junk.mail", 500));
    $rootdir->printList();
} catch (RuntimeException $e) {
    var_dump($e->getTrace());
}

実行結果

$ php Main.php
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

ソースコード

github.com

役割

  • Leaf役: 「中身」を表すクラス。この役の中には他のものを入れることはできない。サンプルコードにおけるFileクラス
  • Composite役: 「容器」を表すクラス。Leaf役やComposite役(自身)を入れることができる。サンプルコードにおけるMyDirectoryクラス
  • Component役: Leaf役とComposite役を同一視するための役。Component役は、Leaf役とComposite役に共通のスーパークラスとして実現する。サンプルコードにおけるEntryクラス
  • Client役: Compositeパターンの利用者。サンプルコードにおけるMain.php

まとめ

  • 再帰的構造
  • 複数と単数の同一視

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門