Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

More than 1 year has 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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?