簡単ですぐ使えるarray_multisortの複数並び替えの記述方法

この記事は「データベース結果の連想配列をPHP側でソートしたい」ひと向けの内容になっています。
結論だけ知りたい方は「**実際の記述例**」を見てください。

## 読者対象
- PHPv5.6以上
- 連想配列内をソートしたい
- 複数の指定がしたい（指定数は可変でやりたい）

## まずはコード例から理解する

[PHP array_mutli_sort][sort_url]

上記公式にある「データベースの結果をソートする」の例をベースにします。
（リンク先にとばなくても問題ありません）

### データ構造

|id|code|name|
|---|---|---|
|1|E6B722|きいろ|
|2|74367D|むらさきいろ|
|3|FF0000|あかいろ|
|4|DF3C94|ももいろ|

### データを可視化（デバッグ時の表示構造）

```
array (
  0 => array(
    'id' => 1,
    'code' => 'E6B722',
    'name' => 'きいろ',
  ),
  1 => array(
    'id' => 2,
    'code' => '74367D',
    'name' => 'むらさきいろ',
  ),
  2 => array(
    'id' => 3,
    'code' => 'FF0000',
    'name' => 'あかいろ',
  ),
  3 => array(
    'id' => 4,
    'code' => 'DF3C94',
    'name' => 'ももいろ',
  )
);
```

また、`$sortList`という配列にはあらかじめソート順を指定したデータを入れておきます。

```php
$sortList[] = array('key' => 'id', 'sort' => SORT_ASC);
$sortList[] = array('key' => 'code', 'sort' => SORT_DESC);
```

今回は２つのソートを指定しました。  
SQLに例えると、下記のようなイメージです。  

```sql
ORDER BY id ASC, code DESC;
```

### 実際の記述例

```php
// DBのデータを$dataに代入しているというイメージで読んでください
$data = $model->get()->toArray();
$result = $this->getSortArray($data, $sortList);

private function getSortArray($data, $sortList)
{
  $argument = [];

  if (empty($sortList)) {
    return $data;
  }

  foreach ((array)$sortList as $list) {
    $argument[] = array_column($data, $list['key']);
    $argument[] = $list['sort'];
  }

  $argument[] = $data;
  array_multisort(...$argument);

  return end($argument);
}
```

カラムがもっと多く、更に順序も２つではなく４つ指定したい！という人は`$sortList`に条件を追加してあげれば解決します。  

結果だけ知りたい、という方は上記の処理内容で終わりになります。  
以降は処理についての簡単な解説をしていきます。  

## array_multisortについて

まずは冗長になりますが、array_multisortの理解からです。  
array_multisortは複数の配列や多次元配列をソートしてくれます。  
そして公式サイトを見たことがある方は、引数の多さに混乱したかもしれません。  
なのでまずは整理をしてみました。

```php
array_multisort(
  引数１： 並び替えしたい配列を指定  | 必須
  引数２： SORT_ASCかSORT_DESCを指定 | 省略可
  引数３： ソート時のデータ形式を指定  | 省略可
  ...
  （以下、上記と同じ指定方法）
)
```

このように、実は意味合いが違う３つの引数指定があるだけです。  
４つ目の引数からは、また追加の配列を指定し、ソート順を指定して…という風になるだけです。大切なのは３つです。  
つまり、シンプルに考えるとただの配列もソートができるわけですね。  
そして究極を言えば、下記でも動きます。

```php
$array = ['2', '1', '3'];
array_multisort($array);
// array(0 => '1', 1 => '2', 2 => '3')
```

引数２を省略した場合は昇順になると公式に書いています。  
なのでシンプルに昇順に並び替えたいなら、上記のような指定でおわりです。  

## 多次元のソートは引数４以降の仕様を理解する

さて、ここまでの理解が得られたなら、次は引数４で指定ができる追加配列についての仕様を理解していきます。  
追加配列は、それ以前のソートを**引き継ぐ**ことができます。  

```php
$array = ['2', '1', '3'];
$array2 = ['1', '2', '3'];
array_multisort($array, $array2);
// array(0 => '1', 1 => '2', 2 => '3')
// array2(0 => '2', 1 => '1', 2 => '3')
```

上記は引数２と引数３を省略し「ソートしたい配列」を２つ指定しています。  
結果を見てもらうと分かる通り、`$array2`は`$array`と同じ順番でソートされています。つまりソートを引き継いでいます。  
この特性を応用すると、連想配列の複雑なソートを実現することができます。

## 上記処理の解説

ではようやく今回のプログラムの解説に移ります。

```php
$data = $model->get()->toArray();
$result = $this->getSortArray($data, $sortList);

private function getSortArray($data, $sortList)
{
  // ※１
  $argument = [];

  if (empty($sortList)) {
    return $data;
  }

  // ※２
  foreach ((array)$sortList as $list) {
    $argument[] = array_column($data, $list['key']);
    $argument[] = $list['sort'];
  }

  // ※３
  $argument[] = $data;
  // ※４
  array_multisort(...$argument);

  return end($argument);
}
```

### ※１について

```php
$argument = [];
```

可変の引数をこの配列に格納するため宣言しています。  

### ※２について

`$sortList`の定義をおさらいします。

```php
$sortList[] = array('key' => 'id', 'sort' => SORT_ASC);
$sortList[] = array('key' => 'code', 'sort' => SORT_DESC);
```

`key`は、「連想配列内のどのプロパティ名でソートするのか」を意味します。  
また`sort`はarray_multisortの引数２に該当します。

```php
foreach ((array)$sortList as $list) {
  // 引数１にソートしたい配列を指定
  $argument[] = array_column($data, $list['key']);
  // 引数２に並び順を指定（ 昇順or降順 ）
  $argument[] = $list['sort'];
  // 引数３は省略
}
```

引数１と引数２のループが上記でされます。  
引数２や引数３は配列ではないので、そこに配列が指定されると自動的に引数１である「ソートしたい配列」として認識してくれます。  

ちなみに、

```php
$argument[] = array_column($data, $list['key']);
```

はPHPv5.5以降で使える構文で、

```php
foreach ((array)$data as $row) {
  // ex) $row: ['id' => 1, 'code' => 'E6B722', 'name' => 'きいろ']
  $argument[] = $row[$list['key']];
}
```

上記のforeachと同じ結果を返してくれます。２重ループの見た目にならなくてスッキリします。

### ※３について

```php
$argument[] = $data;
```

本当にソートしたい配列を１番最後に指定します。  
この配列は、それ以前の引数で指定された配列の順序をすべて引き継いだソートが適用されます。  

### ※４について
```php
array_multisort(...$argument);
```

PHPのv5.6から使うことができる可変長引数を使用しています。  

[PHP 関数の引数][spred_url]

あまり用途がない為、見慣れないかもしれません。ですがこの書き方がarray_multisortと相性が良く、スマートな処理になりました。


## アンチパターン
ちなみに、やらかしたパターンを紹介します…。

```php
$data = $model->get()->toArray();
$result = $this->getSortArray($data, $sortList);

private function getSortArray($data, $sortList)
{

  foreach ((array)$sortList as $list) {
    $array[] = array_column($data, $list['key']);
    // １回目にidでソート、２回目にcodeでソート
    array_multisort($array, $list['sort'], $data);
  }

  return $data;
}
```

上記の場合は、`id`でソートした結果の`$data`に、更に`code`でソートをしています。ですが上記は意図したとおりに動きません。  
SQLで例えると、

```sql
ORDER BY id ASC, code DESC
```

このような結果を期待しているのですが、


```sql
ORDER BY code DESC
```

最後のソートだけが適用されます。  
もし良さげでしたら、ぜひ**可変長引数**を使ってみてください。

## 最後に
ざっと調べた感じ、`array_column`も`array_multisort`も速度的に問題なさそうですね。  
`array_multisort`は今までなんとなくで使えていたので、理解できてよかった(・×・)ﾉ


[sort_url]: https://www.php.net/manual/ja/function.array-multisort.php

[spred_url]: https://www.php.net/manual/ja/functions.arguments.php