業務アプリ開発講座

パンくずリストの生成

前回、ちょっと寄り道をしましたが、今度こそパンくずリストです。そういえば、なんで現在位置を示す1行しかないナビゲーションをパンくずというのでしょうか? Wikiによると童話「ヘンゼルとグレーテル」で、主人公が森で迷わないように通った後にパンくずを残しておいたことに由来するそうです。森でパンくずを置いていったら鳥に食べられてしまって、結局迷子になるように思えるのですが、どうでしょう。毎日庭に訪れる鳥たちを観察している私としては、鳥達の目聡さに感心するばかりで、「森でパンくず」では不安になりますが。

さて、パンくずリストをプログラミングしていきましょう。2つの関数を作成します。

//取得したパンくずデータからHTMLを生成
private static function breadClumbs(){
}
//sitemap.jsonと表示するページのurlからパンくずに必要なデータを抽出
private static function currentClumbs(){
}

これら関数は、例えばスタッフ一覧画面を表示した際に、パンくずリストは「Home / マスタ管理 / スタッフ一覧」と表示されるようにHTMLタグを出力すれば良いのです。

まず先に、関数currentClumbs()を作成します。例えば、スタッフ一覧画面を表示した際には、この関数は、下記のデータを抽出できるようにします。

array(2) {
    [0] => array(1) {
        ["マスタ管理"] => string(0) ""
    }
    [1] => array(1) {
        ["スタッフ一覧"] => string(21) "master/staff-list.php"
    }
}

関数currentClumbs()前回のphpファイル自動生成と同様、無名関数と再帰を使います。まず、関数内に下記ソースを記述します。

$getClumbs = function ($searchElem, $array) use (&$getClumbs) {

}
$sitemap = AppData::sitemap(JsonDecodeType::TYPE_ARRAY);
$current = filter_input(INPUT_SERVER, "SCRIPT_NAME");
$GLOBALS["clumbs"] = [];

//ここで無名関数が格納された変数[$getClumbs]を実行している
if($getClumbs($current, $sitemap)) return $GLOBALS["clumbs"];
else                               return [];
$getClumbs = function ($searchElem, $array) use (&$getClumbs) {

再帰処理をする無名関数を変数$getClumbsに代入しています。引数$searchElmには検索する文字列、ここではページのURLが入ります。スタッフ一覧画面であれば、/manster/staff-list.phpという文字列です。2つ目の引数$arrayは検索対象の配列データデータです。ここではsitemap.jsonを変換した連想配列です。useは前回のPHPファイル自動生成で解説しています。

この無名関数を以降で呼び出しています。先に無名関数を定義し、その後にこの無名関数を呼び出します。
変数$sitemapはsitemap.jsonを連想配列に変換されたデータです。

次の変数$currentは現在表示しているページのスクリプト名です。先にも書きましたが、スタッフ一覧ページでしたら、/manster/staff-list.phpという文字列です。

グローバル変数$GLOBALS["clumbs"]は、無名関数で抽出したパンくず関連情報を格納します。なぜこのようなことをするかというと、sitemap情報した際に、最後にヒットした情報だけを欲しいのではなく、検索にヒットした要素の階層情報、つまり親要素の上も欲しいからです。しかもそれらを配列情報として取得したいがためです。そこに関しては、この無名関数の内容で解説します。


最後にこの無名関数$getClumbs()を実行しています。検索にヒットすればその内容を、ヒットしなければ要素なしの配列を返します。

無名関数の中身です。

foreach ($array as $key => $value) {
    if(is_array($value)){
        if($getClumbs($searchElem, $value)){
            $name = $url = '';
            if(array_key_exists("name", $value))    $name = $value["name"];
            if(array_key_exists("url",  $value))    $url  = $value["url"];

            if($name !== '')    array_unshift($GLOBALS["clumbs"], [$name => $url]);
            return true;
        }
    }
    else{
        if (strripos($searchElem, $value) !== false){
            return true;
        }
    }
}
return false;

sitemap.jsonの「Home」から順番に走査していきます。まず値が配列かそうでないかで処理を振り分けてます。sitem.jsonで言えば、child要素かそうでないかになります。配列であれば、再度自分自信を呼び出しています。再帰処理ですね。

現在のスクリプト名とsitemapの属性urlが一致するかどうかは、

if (strripos($searchElem, $value) !== false){

ここで評価されます。ここで一致すると、無名関数はtrueを返します。つまり、3行目の

if($getClumbs($searchElem, $value)){

この条件にマッチすることになります。

この再帰処理では、要素の値が配列であれば、順番に深く階層を探っていき、一致する要素があれば、順番に階層を遡ってtrueを返して生きます。そしてこの無名関数がtrueを返した場合、その要素の情報をグローバル変数$BLOBALS["clumbs"]へ追加して生きます。その際、深い階層から浅い階層へ順に処理されて生きますので、グローバル変数$BLOBALS["clumbs"]へは、配列の末尾ではなく先頭に追加していきます。そうすることで、正しい階層を再現できるのです。


stock-1

これで、sitemap.jsonからパンくずリストデータを取得できるようになりました。続いてはそのデータからパンくずhtmlを生成します。

まず、classTempMasterHtmlに下記定数を追記します。

const BREAD_CLUMBS_BLOCK = <<<DOC
<ul class='breadcrumb'>
	%s
</ul>
DOC;

続いて、パンくずHtml生成関数を示します。

private static function breadClumbs(){

    $clumbs = AppMasterHtml::currentClumbs();
    $tmpTag = TempMasterHtml::BREAD_CLUMBS_BLOCK;

    if(count($clumbs) <= 1){
        return sprintf($tmpTag, "<li class='active'>Home</li>");
    }

    $current = filter_input(INPUT_SERVER, "SCRIPT_NAME");
    $tags    = ["<li><a href='index.php'>Home</a></li>"];

    foreach($clumbs as $clumb){

        if(!is_array($clumb))    continue;

        foreach($clumb as $key => $value){

            if($value == ""){
                $tags[] = "<li>{$key}</li>";
                continue;
            }
            if(strripos($current, $value)){
                $tags[] = "<li class='active'>{$key}</li>";
                continue;
            }
            $tags[] = "<li><a href='{$value}'>{$key}</a></li>";
        }
    }
    return sprintf($tmpTag, implode("\n", $tags));
}
$clumbs = AppMasterHtml::currentClumbs();

先ほど作成した、パンくずデータを取得する関数を呼び出して、パンくずデータを取得します。

if(count($clumbs) <= 1){
    return sprintf($tmpTag, "<li class='active'>Home</li>");
}

パンくずデータが1件以下の場合、「Home」メニューだけを書き出して処理を終了します。ここで改めてパンくずリストの要件を定義しておきましょう。

  1. Homeは常に含める。
  2. 現在のページにはLinkを付加せず、activeスタイルを適用する。

そして、パンくずリストを取得する関数currentClumbs()では、「Home」以外のページにアクセスした場合、Home情報は含まれません。関数currentClumbs()で取得したデータのみに従ってパンくずリストを生成すると条件1を満たすことができません。そして、「Home」情報が返ってくる場合というのは「Home」ページにアクセスした場合のみで、且つ要素数は必ず1です。つまり、「Home」にアクセスした場合と、未設定のページ(sitemap.jsonにデータがない)にアクセスした場合には、Linkなしの「Home」メニューのみを表示する、となります。

以降はデータに準じて順番にHtmlを生成しています。

最後にclassAppMasterHtmlの関数header()

echo AppMasterHtml::breadClumbs();

を追記すれば完成です。

次回は「Home」のボディメニューを作成します。


Leave a Comment