PHPで階層の不確定な多次元配列の処理をしてみたのでその覚書です。

この記事は普段PHPを触る頻度が多いけれどもエキスパートではない私のような量産型プログラマーを対象としています。

同一階層で保存してあるが親の指定があるケース

WordPressでは固定ページやカテゴリーなど、項目としては並列に保存しているけれど、親がどの項目か指定されている形式がよくとられています。

$raw_array = array(
	'a1' => array(),
	'a1-1' => array(
		'parent' => 'a1',
	),
	'a1-1-1' => array(
		'parent' => 'a1-1',
	),
	'a1-1-2' => array(
		'parent' => 'a1-1',
	),
	'a1-2' => array(
		'parent' => 'a1',
	),
	'a2' => array(),
	'a2-1' => array(
		'parent' => 'a2',
	),
);

しかし実際の出力時にはカテゴリーの親子などのように、親子関係がわかるように階層をも持たせて出力したい。

配列で言えば以下のように多次元の連想配列にして出力すしたいケースです。

$array = array(
	'a1' => array(
		'a1-1' => array(
			'a1-1-1' => array(),
			'a1-1-2' => array(),
		),
	),
	'a2' => array(
		'a2-1' => array(),
	),
);

とりあえず上記のような階層を持った配列に加工できたと仮定して、これをHTMLのリスト形式で出力しようとすると次のように処理の入れ子になりますが、階層の数に合わせて処理を繰り返す必要があります。
しかしながら、階層の深さは変動する場合に自動で処理する方法がわからずに困っていました。

if ( count( $array ) ){
	echo '<ul>';
	foreach ( $array as $key => $second ) {
		echo '<li>'.$key;
		if ( count( $second ) ){
			echo '<ul>';
			foreach ( $second as $key => $third ) {
				echo '<li>'.$key;
				if ( count( $third ) ){
					echo '<ul>';
					foreach ( $third as $key => $fourth ) {
						echo '<li>'.$key;
						if ( count( $fourth ) ){
							foreach ( $fourth as $key => $fifth ) {
								echo '<li>'.$key;	
								// どうやってループすれば...			
							}
							echo '</ul>';
						}
						echo '</li>';
					}
					echo '</ul>';
				}
				echo '</li>';
			}
			echo '</ul>';
		}
		echo '</li>';
	}
	echo '</ul>';
}

一定の法則での繰り返しなので、何かしら自動処理が出来るはずだと試行錯誤の結果…

function display( $array = array() ){
	if ( is_array( $array ) && count( $array ) ){
		echo '<ul>';
		foreach ( $array as $key => $child ) {
			echo '<li>'.$key;
				display( $child );
			echo '</li>';
		}
		echo '</ul>';
	}
}
display( $array );

このように、関数の中で自分(の関数)を呼び出すという処理で実現できます。

いろいろ調べていた時に配列の階層を調べる関数のサンプルがあって、これを初めて見た時は
「なんじゃこりゃ!?関数の中で自分を呼び出せるの!?」という衝撃でしたが、これを応用する事で実現できました。

並列の配列から多階層への整形

先の出力の方法と同じ要領で、単一階層の配列から、親子関係を持つ配列を生成します。

以下のコードで配列の書き換えが実現できました。

// 新しく作る階層を持った配列
$new_array = array();

// 先祖階層は parent が 空になって下層とは処理が違うのでまずは先祖階層だけの新しい配列を作成
// 元配列をループ {
foreach ( $raw_array as $key => $value ) {
	// 親指定がない場合 {
	if ( empty( $value['parent'] ) ){
		// 新しい配列に格納
		$new_array[$key] = array(); 
	}
}

// 下階層を辿って整形する関数
function create_nest_array( $array = array(), $raw_array ){
	if ( is_array( $array ) && count( $array ) ){
		foreach ( $array as $key => $child ) {
			// 元の配列をループ {
			foreach ( $raw_array as $raw_key => $value) {
				// ループ中の $key を 親に項目に指定している項目(子)がある場合 {
				if ( ! empty( $value['parent'] ) && $value['parent'] === $key ){
					// 子項目をを新しい配列に格納
					$array[$key][$raw_key] = array();
					// 同様に下階層を処理
					$array[$key] = create_nest_array( $array[$key], $raw_array );
				}
			}
		}
	}

	return $array;
}

$new_array = create_nest_array( $new_array, $raw_array );
print '<pre style="text-align:left">';print_r($new_array);print '</pre>';

WordPressで保持しているデータの処理でわりと使い所があるのではないかなと思いつつ、考えるよりもとりあえず4〜5回ネストした方が頭が混乱しなくてメンテナンス性がいいのかもしれません…。

所感

何年か前にも一度、カスタム分類を使った階層のある同様の処理が必要な場面に遭遇して、でも解決策がわからず、実際問題そんなに階層が深くなる事がないので「とりあえず5回くらい入れ子にしておくか」で済ませていたのですが、いい加減出来るようになっているはずだと思って今回再トライしてみました。

末端階層から辿ったり、元の配列をwhileで回しながら処理したらunsetするとか試行錯誤の上 “関数の入れ子” というやり方を知ってようやく実現できました!

こういう処理はおそらく情報処理を大学等で学んだ人には常識だったりするのかもしれませんが、自分としてはなぞなぞがようやく解けた感じで満足です!

最下層から辿る処理の残骸

function veu_noindex_targets(){
	$array = array( 
		'is_archive' => array(
			'label' => __( 'Archive', 'vk-all-in-one-expansion-unit' ),
			'parent' => '',
			'para' => null,
		),
		'is_date' => array(
			'label' => __( 'Date and time archive', 'vk-all-in-one-expansion-unit' ),
			'parent' => 'is_archive',
		),
		'is_year' => array(
			'label' => __( 'Yearly archive', 'vk-all-in-one-expansion-unit' ),
			'parent' => 'is_date',
		),
		'is_author' => array(
			'label' => __( 'Author archive', 'vk-all-in-one-expansion-unit' ),
			'parent' => 'is_archive',
		),
		'is_post_type_archive' => array(
			'label' => __( 'Post type archive', 'vk-all-in-one-expansion-unit' ),
			'parent' => 'is_archive',
		),
		'is_category_and_tax' => array(
			'label' => __( 'Category, Tags and other Custom taxonomis', 'vk-all-in-one-expansion-unit' ),
			'parent' => 'is_archive',
		),
		// 'is_post_type_archive_post' => array(
		// 	'label' => __( 'Post type archive', 'vk-all-in-one-expansion-unit' ) .' [ '. __( 'post', 'vk-all-in-one-expansion-unit' ) . ' ]',
		// 	'parent' => 'is_post_type_archive',
		// 	'init' => 'is_post_type_archive',
		// 	'para' => 'post',
		// ),
	);
	return $array;
}

veu_noindex_targets = veu_noindex_targets();

// 先祖階層
$ancestors = array();
foreach ( $veu_noindex_targets as $key => $value ) {
	if ( empty( $value['parent'] ) ) {
		$ancestors[] = $key;
	}
}

// 子を持つ階層
$parents = array();
foreach ( $veu_noindex_targets as $key => $value ) {
	if ( ! empty( $value['parent'] ) ) {
		$parents[] = $value['parent'];
	}
}

// 末端階層
$bottoms = array(); 
foreach ( $veu_noindex_targets as $key => $value ) {
	// 自分を親に持つkeyが存在しない値
	if ( ! empty( $value['parent'] ) && ! in_array( $key, $parents ) ) {
		$bottoms[] = $key;
	}
}

// 末端階層から先祖階層までのルートを形成
foreach ( $bottoms as $item ) {
	$bottom_up_array[$item] = array();
	// 	その親がトップ階層じゃない時
	if ( ! empty( $veu_noindex_targets[$item]['parent'] ) ){
		$parent = $veu_noindex_targets[$item]['parent'];
		$bottom_up_array[$item][] = $parent;
		while ( $parent ){
			// さらに親の階層を取得
			if ( ! empty( $veu_noindex_targets[$parent]['parent'] ) ){
				$parent = $veu_noindex_targets[$parent]['parent'];
				$bottom_up_array[$item][] = $parent;
			} else {
				$parent = null;
			}
		}
	}
}
print '<pre style="text-align:left">';print_r($bottom_up_array);print '</pre>';


print '<pre style="text-align:left">';print_r($ancestors);print '</pre>';
print '<pre style="text-align:left">';print_r($parents);print '</pre>';
print '<pre style="text-align:left">';print_r($bottoms);print '</pre>';

Follow me!

この記事を書いた人

石川栄和代表取締役
名古屋のウェブ制作会社数社に10年程度務めた後、株式会社ベクトル設立。
企画・運営・コンサルティング〜WordPressを中心としたシステム開発まで幅広く携わる。
[ 著書 ]
・いちばんやさしいWordPressの教本(共著)
・現場でかならず使われているWordPressデザインのメソッド(共著)
[ 最近のWordPressコミュニティでの活動 ]
2019 WordCampNiigata セッションスピーカー
2019 WordCampHaneda セッションスピーカー
2018 WordCampOsaka セッションスピーカー
2017 WordCampKyoto セッションスピーカー

初心者でも簡単!無料WordPressテーマ

100%GPL / 商用利用可能

Lightning は WordPress公式ディレクトリに登録されているカスタマイズ性の高いテーマです。
デモデータも配布されているので、ビジネスサイトの雛形が数分でセットアップできます。

VWSオンラインコミュニティー

オンラインコミュニティでは、より良いテーマ・プラグイン開発のため、機能改善・追加要望などの書き込み大歓迎です!
ユーザー同士の交流や意見交換の場としてもお気軽にご参加ください。
※質問はフォーラムでのみ対応となります。