LoginSignup
2

More than 3 years have passed since last update.

posted at

updated at

Organization

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2