CakePHP3：配列をあれこれするのに便利なHashクラス

いきなりですが、業務でこんなことありませんか？
#### DBからデータを取ってきて、それを用途に合わせて加工したい！

例えばこんなのなど。

- **違う項目で並び替えたい**　　　　→　男性好感度ランキングと女性好感度ランキングを表示
- **指定した条件でデータが欲しい**　→　男性好感度80%以上のデータの表示
- **グループ化したデータが欲しい**　→　地域別好感度を出したい


その時に「都度findするかー:slight_frown:」とか「foreachしてデータ生成するかー。面倒だけど…:weary:」と思われた方。
ありがとうございます。この記事適性MAXでございます。
当記事が参考になれば幸いです。

まず、Hashにはいくつか種類が用意されています。（以下）

|||||
|:-----------------|:-------------------|:-----------------|:----------------|
| Hash::get | Hash::extract | Hash::insert | Hash::remove | 
| Hash::combine | Hash::format | Hash::contains | Hash::check | 
| Hash::filter | Hash::flatten | Hash::format | Hash::expand | 
| Hash::merge | Hash::numeric | Hash::dimensions | Hash::maxDimensions | 
| Hash::map | Hash::reduce | Hash::apply | Hash::sort| 
| Hash::diff | Hash::mergeDiff | Hash::normalize | Hash::nest| 

・・・
書き出してみて「こんなにあったのか。。」と焦っているのは内緒にしつつ、、、
今ここで全部説明するのは時間的にも労力的にも厳しいので、
**実際に業務で使用したもの**、**便利そうだと思ったもの** を抜粋して紹介していきたいと思います。

```言語:今回使用するテーブル
mysql> select * from books;
+----+-----------+-------+-------+-----------+---------------------+---------------------+
| id | name      | price | sales | category  | created             | modified            |
+----+-----------+-------+-------+-----------+---------------------+---------------------+
|  1 | HTML5 Lv1 |   100 |    81 | front_end | 2019-12-01 10:00:00 | 2019-12-01 10:00:00 |
|  2 | HTML5 Lv2 |   500 |    24 | front_end | 2019-12-01 10:00:00 | 2019-12-01 10:00:00 |
|  3 | php Lv1   |   300 |    50 | back_end  | 2019-12-01 10:00:00 | 2019-12-01 10:00:00 |
|  4 | php Lv2   |   200 |   102 | back_end  | 2019-12-01 10:00:00 | 2019-12-01 10:00:00 |
|  5 | MySQL Lv1 |   400 |     5 | server    | 2019-12-01 10:00:00 | 2019-12-01 10:00:00 |
|  6 | MySQL Lv2 |   400 |    30 | server    | 2019-12-01 10:00:00 | 2019-12-01 10:00:00 |
+----+-----------+-------+-------+-----------+---------------------+---------------------+
```

## その1 Hash::sort
これは名前からも想像できるように配列をソートするものです。
前段の例にも上げた「一度取り出したデータ配列を別のキーでソートし直したい」といった事を実現できます。

まず初めにbooksテーブルをidの昇順で取得します。

```言語:sortの使用例

$books = TableRegistry::get('Books');
$query = $books->find();
$query->order(['price' => 'asc']);
$query->all();
$data = $query->toArray();
```
すると上記した「今回使用するテーブル」の通りのデータが採取されます。
そして、売上ランキングを表示するために売上（sales）の降順データも欲しい。という場合はこうするとOK！

```
$result = Hash::sort($data, '{n}.sales', 'desc');
```
以下のようにデータが取れます。

```
	(int) 0 => [
		'id' => (int) 4,
		'name' => 'php Lv2',
		'price' => (int) 200,
		'sales' => (int) 102,
		'category' => 'back_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 1 => [
		'id' => (int) 1,
		'name' => 'HTML5 Lv1',
		'price' => (int) 100,
		'sales' => (int) 81,
		'category' => 'front_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 2 => [
		'id' => (int) 3,
		'name' => 'php Lv1',
		'price' => (int) 300,
		'sales' => (int) 50,
		'category' => 'back_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 3 => [
		'id' => (int) 6,
		'name' => 'MySQL Lv2',
		'price' => (int) 400,
		'sales' => (int) 30,
		'category' => 'server',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 4 => [
		'id' => (int) 2,
		'name' => 'HTML5 Lv2',
		'price' => (int) 500,
		'sales' => (int) 24,
		'category' => 'front_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 5 => [
		'id' => (int) 5,
		'name' => 'MySQL Lv1',
		'price' => (int) 400,
		'sales' => (int) 5,
		'category' => 'server',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	]
```
きちんと売上（sales）の降順データに並んでますね。
もう一度findして取ってこなくていいから楽ちん楽ちん:smile:

## その2 Hash::extract
公式ページの説明文をそのまま記載すると
> extractは好きなパスに沿ったデータを手早く取り出すことができます。
 
・・・直感で理解しがたいですね。
要するに「引数に条件を指定してそれを満たすデータを配列から取り出すことができる」という事です。

例えば、「今回使用するテーブル」から売上が50以上のものが欲しい。という時は、

```
$result = Hash::extract($data, '{n}[sales>=50]');
```
としてあげる事で以下のようにデータが取れます。

```
	(int) 0 => [
		'id' => (int) 1,
		'name' => 'HTML5 Lv1',
		'price' => (int) 100,
		'sales' => (int) 81,
		'category' => 'front_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 1 => [
		'id' => (int) 3,
		'name' => 'php Lv1',
		'price' => (int) 300,
		'sales' => (int) 50,
		'category' => 'back_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	],
	(int) 2 => [
		'id' => (int) 4,
		'name' => 'php Lv2',
		'price' => (int) 200,
		'sales' => (int) 102,
		'category' => 'back_end',
		'created' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		},
		'modified' => object(Cake\I18n\FrozenTime) {

			'time' => '2019-12-01T10:00:00+09:00',
			'timezone' => 'Asia/Tokyo',
			'fixedNowTime' => false
		
		}
	]
```

## その3 Hash::combine
この機能では、キーと値を自由に指定して配列を組み替えることができます。
また、グループを指定する事でそれに従ってグルーピングされたデータを取得することができます。

例えば、カテゴリーごとにグルーピングしてデータを取得したい場合は、

```
$result = Hash::combine($data, '{n}.id', '{n}', '{n}.category');
```
とする事で以下のようにグルーピングされたデータが取得できます。

```
	'front_end' => [
		(int) 1 => [
			'id' => (int) 1,
			'name' => 'HTML5 Lv1',
			'price' => (int) 100,
			'sales' => (int) 81,
			'category' => 'front_end',
			'created' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			},
			'modified' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			}
		],
		(int) 2 => [
			'id' => (int) 2,
			'name' => 'HTML5 Lv2',
			'price' => (int) 500,
			'sales' => (int) 24,
			'category' => 'front_end',
			'created' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			},
			'modified' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			}
		]
	],
	'back_end' => [
		(int) 3 => [
			'id' => (int) 3,
			'name' => 'php Lv1',
			'price' => (int) 300,
			'sales' => (int) 50,
			'category' => 'back_end',
			'created' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			},
			'modified' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			}
		],
		(int) 4 => [
			'id' => (int) 4,
			'name' => 'php Lv2',
			'price' => (int) 200,
			'sales' => (int) 102,
			'category' => 'back_end',
			'created' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			},
			'modified' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			}
		]
	],
	'server' => [
		(int) 5 => [
			'id' => (int) 5,
			'name' => 'MySQL Lv1',
			'price' => (int) 400,
			'sales' => (int) 5,
			'category' => 'server',
			'created' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			},
			'modified' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			}
		],
		(int) 6 => [
			'id' => (int) 6,
			'name' => 'MySQL Lv2',
			'price' => (int) 400,
			'sales' => (int) 30,
			'category' => 'server',
			'created' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			},
			'modified' => object(Cake\I18n\FrozenTime) {

				'time' => '2019-12-01T10:00:00+09:00',
				'timezone' => 'Asia/Tokyo',
				'fixedNowTime' => false
			
			}
		]
	]
```

以上、簡単ではありますが、
便利だと思った機能について紹介させてもらいました。

## 最後に
Hashにはまだまだ便利な機能があるので公式ページを参考にしてみてください。
https://book.cakephp.org/3/ja/core-libraries/hash.html

実際に中でどのように動いているかを知りたい人は、、、コアを覗いてみてください。ﾌﾌ
/scripts/vendor/cakephp/cakephp/src/Utility/Hash.php