連想配列の使いまわしによるミスやロスを防ぐための値オブジェクト

  • 2020年3月4日
  • PHP

PHPの配列、JavaScriptのオブジェクト、Pythonの辞書型、JavaのMapなど多くのプログラミング言語では”ある値とある値の紐づけの集合”である連想配列の考えを持った機能が用意されています。
PHP: 配列 – Manual
JavaScript オブジェクトの基本 – ウェブ開発を学ぶ | MDN
5. データ構造 — Python 3.8.2 ドキュメント#5.5. 辞書型 (dictionary)
Map (Java Platform SE 8 )
 連想配列を実現する機能は強力な機能であり、複雑な値を一度に抽象的に取り回せます。連想配列がよく使われる状況の一つに外部との複雑なやり取り(APIリクエストの受付、SQL実行結果の読み取りなど)があります。こういった時に外部情報が全く同じ型の一変数にまとめられているのは連想配列のおかげで、大体連想配列を実現している型やその拡張で返ってきます。
 便利な連想配列ですが弱点もあります。それは、ただそこに連想配列があるという情報からはキーが何なのか全く分からない、という点です。例えば、PHPのHTTP POST リクエスト変数$_POSTがあります。
PHP: $_POST – Manual
 $_POSTの中にはHTTP POST メソッドから現在のスクリプトに渡された変数の連想配列です。ざっくばらんに言えばPOSTメソッドで送ったフォームデータとかそんな感じです。$_POSTは常にどの様なリクエストでも格納するため都度キーの異なる連想配列になります。

// ある通信では
dd($_POST);
/**
 * array: 3 [
 *   "name"  => "浜松 太郎",
 *   "email" => "taro@example.com",
 *   "token" => "czLPe9ARxnUkEkxkVFjf9JfwI",
 * ]
 */
// また別の通信では
dd($_POST);
/**
 * array: 2 [
 *   "action" => "edit",
 *   "body"   => array: 3 [
 *       "id"      => 3,
 *       "checked" => true,
 *       "tel"     => "08012345678",
 *   ],
 * ]
 */

 こういった時、連想配列にあるキーが入っていることを期待してコーディングをすることがよくあります。

$post = $_POST;
// $post['token']に存在していて欲しい処理
if(isset($post['token']) && $post['token'] === secToken() ){
    // なんか処理
}

 ちょっとしたコードならこれでも十分で便利なのですが、時には長いデータフローの中で同じ連想配列を扱い続けることもあります。そういった時、名前指定ミスが起きることもあります。また同じ情報源に対して同じ加工をする時もあります。

$post['name_kana'] // 実際は$post['kana_name']
// ある場所で
$post['last_name'] .' '. $post['first_name'];
// またある場所で
$fullName = $post['last_name'] .' '. $post['first_name'];

 こういったことが起き出すと大体似たことが続けて起き出します(ハインリッヒの法則 – Wikipedia的な感じ)。こうなってくると根本的な予防策が欲しくなります。値オブジェクトがこの問題の解決策の一つです。
 手っ取り早くarrayをオブジェクトに変えるだけでも予測補完が効いてかなりやりやすくなります。また多彩な表現をつけるプレゼンターパターン的にするのも便利です。例えば次です。

class MemberCreateRequest{
{
	/** @var \DateTime 生年月日 */
	public $birthday;
	/** @var string 名前かな */
	public $name_kana;
	/** @var string 名前 */
	public $name;

	/**
	 * @param array $post
	 * @throws \Exception
	 */
	public function __construct(array $post)
	{
		$this->name = $post['name'];
		$this->name_kana = $post['name_kana'];
		$this->birthday = new \DateTime($post['birthday']);
	}
	
	
	/**
	 * @param int $index
	 * @return false|string 前から数えた$index桁目の生年月日の年
	 */
	public function birthday_year_digit($index = 0)
	{
		return $this->birthday->format('Y')[$index];
	}
}

 構造体に毛が生えた様なシンプルなクラスですが、これだけでも誤りと重複を減らせます。また次の様に型の強制をさせることで妙な動作を引き起こす記述をしにくくできます。

	private function create_notifyr_fax(MemberCreateRequest $post)
	{
		// faxの生成処理。絶対にMemberCreateRequestクラスのインスタンスなので名前間違い等起きにくい
	}
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG