業務アプリ開発講座

sitemap.jsonからphpファイルの自動生成

前回、パンくずリスト生成プログラムを作成すると予告しましたが、現段階では、index.phpしか作成しておりませんので、パンくずリストが正しく機能しているかどうかを確認しにくい状態にあります。ですので、ちょっと横道にずれますが、sitemap.jsonを使って、その他のPHPファイルを自動で生成プログラムを作成します。このプログラムで作成する関数は、パンくずリストを生成する際も流用できる部分のありますし、ファイルを一つ一つ手作業で作成する必要も無くなります。作っておくと便利でしょう。

ということで、managerディレクトリ以下に下記フォルダとファイルを作成してください。

/etc/temp.php
/etc/make-file.php

temp.phpは自動作成ファイルの雛形で、このtemp.phpをsitemap.jsonのパスやファイル名に従ってコピーするわけです。

まずはtemp.phpのソースを示します。

<?php
require_once('../../includes/Init.php');
?>
<?= AppMasterHtml::header() ?>

<!-- MainContents -->
<div class="container-fluid">

</div><!-- /MainContents -->

<?= AppMasterHtml::externalJs() ?>
<script type="text/javascript">

</script>
</body>
</html>

このソースをよく見ると、今までに作成していない関数が2つほど見受けられます。

AppMasterHtml::header()

これは、これまでに作成した関数htmlHeader()navHeader()をまとめて呼び出す関数です。
これまでは、index.php上に下記にように記述していました。

<?= AppMasterHtml::htmlHeader() ?>
<?= AppMasterHtml::navHeader() ?>

ここにパンくずリスト生成関数も加えると

<?= AppMasterHtml::htmlHeader() ?>
<?= AppMasterHtml::navHeader() ?>
<?= AppMasterHtml::breadClumbs() ?>

となるところですが、すべての画面系phpファイルでいちいち3行も書くのも面倒ですので、それらをまとめて呼び出す関数を作成し、画面系phpでは1行で済ませるようにします。

class AppMasterHtml内に新たに、関数header()を作成してください。ついでに後日示します、パンくずリスト生成関数breadClumbs()も中身なしで、枠だけ作成しておいてください。

public static function header(){

	echo AppMasterHtml::htmlHeader();
	echo AppMasterHtml::navHeader();
	echo AppMasterHtml::breadClumbs();
}
private static function breadClumbs(){
}

関数htmlHeader(), navHeader()のアクセス修飾子はこれまで、publicでした。これは画面系PHPからアクセスしていましたので、そうしていたわけですが、関数header()をかませるように変更したことによって、関数htmlHeader(), navHeader()の呼び出しが、関数header()からだけ、となりました。ですので、どこからでもアクセスできるようにしておく必要がなくなりました。そこで、関数htmlHeader(), navHeader()のアクセス修飾子をpublicからprivateへ変更します。これで、この二つの関数はclassAppMasterHtml内でのみアクセスできるようになります。なぜわざわざアクセス修飾子をprivateに変更するかと、すべてpublicでいいじゃないかと思われるかもしれませんが、アクセスを制限することで不要な呼び出しを避け、より安全に構築し、関数がどういう性質のものであるかを明示することができます。

プログラムソースはコンピュータに対する命令であると同時に、開発者(人)に対する考えや意思の表現でもあるのです。

もう一つの関数AppMasterHtml:externalJs()は外部javascriptの読み込みタグを一括で管理するものです。これも追加しておきましょう。

public static function externalJs(){

	return <<<DOC
<script src="../assets/js/jquery-2.1.4.js" type="text/javascript" charset="utf-8"></script>
<script src="../assets/js/jquery.validate.js" type="text/javascript" charset="utf-8"></script>
<script src="../assets/js/messages_ja.js" type="text/javascript" charset="utf-8"></script>
<script src="../assets/js/bootstrap.js" type="text/javascript" charset="utf-8"></script>
<script src="../assets/js/bootstrap-confirmation.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../assets/js/bookmin.js" type="text/javascript" charset="utf-8"></script>
DOC;
}

前置きが長くなりました。本題のphpファイル自動生成プログラムです。make-file.phpに下記を記述します。

<?php
require_once('../../includes/Init.php');

$makePhp = function ($nav) use (&$makePhp) {

    $tempPath = $GLOBALS['path']["root"].'/manager/etc/temp.php';
    $itemPath = $GLOBALS['path']["root"].'/manager';

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

        if(is_array($value)){
            $makePhp($value);
            continue;
        }

        if($key != 'url')	continue;

        $pathItems	= explode('/', $value);
        foreach($pathItems as $item){

            $itemPath	.= '/'.$item;
            if(file_exists($itemPath)) continue;

            if(preg_match('/.php$/', $itemPath)){
                if(!copy($tempPath, $itemPath))   echo 'Copy 失敗';
            }
            else{
                if(!mkdir($itemPath, 0777, true)) echo 'Dir 作成失敗';
            }
        }
    }
};

$nav	= AppData::sitemap(JsonDecodeType::TYPE_ARRAY);
$makePhp($nav);
?>
require_once('../../includes/Init.php');

前回までに作成したInit.phpをrequire_onceしています。これで、$GLOBALS変数やincludes内に作成した各種プログラムを自由に潰えるようになります。

$makePhp = function ($nav) use (&$makePhp) {

一風変わった書き方が出てきました。変数$makePhpの右辺にいきなりfunctionが出てきました。こういう書き方を無名関数と言います。通常functionの次に何かしらの関数名を書きますね。ここではfunctionの次にきなり括弧で引数を書いています。Javascriptではおなじみのものなのですが、phpではバージョン5.3以降で使えるようになった機能です。関数の機能を変数に格納する書き方です。そしてその右横にuse (&$makePhp)となっています。これは、再帰処理をする際に必要になるもので、無名関数だから必要になるというわけではありません。

「再帰」とは関数が自分自身を呼び出すことを言います。この関数で言いますと、foreach内で自分自身$makePhpを読んでいますね。こういうことを「再帰」と言います。階層構造になっているデータを走査する場合に用いられることの多い手法です。例えば、どのディレクト内にあるかわからないファイルを検索する場合に、用います。

そして、useは、無名関数の外にある変数を無名関数にあの渡すための仕組みで、php特有の変数のスコープに由来します。phpは関数の外で設定された変数は引数で渡すかグローバル変数にしないと関数内では参照できませんでしたね。ですので、再帰処理をする場合は、無名関数に自分自身への参照を渡す必要があるのです。そしないと再帰処理で、下の階層へ処理が映った場合に、未定義の変数をすることになってエラーになります。この辺りはプログラミングに慣れてこないと感覚をつかみにくいでしょう。ですので、再帰処理をしたい場合はuseを使うくらいに覚えておけばよいかと思います。

$tempPath = $GLOBALS['path']["root"].'/manager/etc/temp.php';
$itemPath = $GLOBALS['path']["root"].'/manager';

変数$tempPathはコピー元のtemp.phpの絶対パスです。
変数$itemPathはコピー先の絶対パスです。managerディレクトリ以下に新しくphpファイルが作られます。managerディレクトリ以下のディレクトリやファイル名はsitemap.jsonのurl要素に応じて、この変数に追加されていきます。

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

    if(is_array($value)){
        $makePhp($value);
        continue;
    }

引数の配列データを順番に処理していきます。最初のif文if(is_array($value))は値が配列だったらという意味になりますね。つまり、sitemap.jsonの場合、値がはい列になる要素は、キーがchildの要素ですね。そうしたら、自分自身を再度呼んでいます。ここが再起処理になります。そして、今度は引数にchildキーの値を引数として渡しています。順番に要素を処理して行き、値が配列だったらを繰り返すことで深い階層のデータでも順番に同じ処理を繰り返すことができるわけです。

一番最初の配列データとは、Homeや売上管理がある階層ですね。この第1階層を順番に検査して行きます。その時「売上管理」には、child要素があります。すると今度は、売上管理のchild要素の値を引数にして、自分自身、つまり$makePhp関数を呼び出します。このように配列要素が出現するたびに自分自身を再度呼び出すことで、階層的なデータを順番に処理できるわけです。
その次のcontinueはforeachなどのループ処理を、途中で次の要素へ移す命令です。

if($key != 'url') continue;

phpファイルを作成する機能ですので、ファイル名が書かれているurl要素が欲しいわけです。url以外は必要のない要素ですので、次の要素へ処理を移します。

$pathItems = explode('/', $value);

前の行でurl要素以外は次へ処理を写しましたね。ということは、この行はurl要素だということです。urlの値を/で文字列を分割し、配列データにします。例えば、値がsales/daily-summary.phpだとすると、['sales', 'daily-summary.php' ]に変換されます。このうち末尾の要素はファイル名で、それより前の要素はディレクトリ名ということになります。

foreach($pathItems as $item){

    $itemPath .= '/'.$item;
    if(file_exists($itemPath)) continue;

    if(preg_match('/.php$/', $itemPath)){
        if(!copy($tempPath, $itemPath))   echo 'Copy 失敗';
    }
    else{
        if(!mkdir($itemPath, 0777, true)) echo 'Dir 作成失敗';
    }
}

分割した要素で順番に処理していきます。関数内の最初に設定していた変数$itemPathに、先で生成した配列要素を順番に結合していきます。最初のif(file_exists($itemPath))は引数のファイルあるいはディレクトリが既に存在しているかをチェックしています。既に存在する場合は、処理をスキップします。そうしないと既に作成しているphpプログラムを上書きして、これまでの仕事を無にしていまいます。

次のif(preg_match('/.php$/', $itemPath))は、生成したパスの末尾が.phpかをチェックしています。つまりディレクトリではなく、phpファイルであるかをチェックしてます。phpファイルであれば、temp.phpファイルをコピーします。phpファイルでなければ、ディレクトリパスということになりますので、そのディレクトリを作成します。先の['sales', 'daily-summary.php' ]で説明しますと、まず、salesディレクトリがなければ、salesディレクトリを作り、次にdaily-summary.phpファイルをコピーします。

ここまでは関数に定義内容についての解説でした。最後の

$nav	= AppData::sitemap(JsonDecodeType::TYPE_ARRAY);
$makePhp($nav);

ここで、sitemapデータを取得し、初めて、関数$makePhp()を実行しています。

それではブラウザでetc/make-file.phpにアクセスしましょう。sitemap.jsonに定義されているファイルが自動で生成されるはずです。

このプログラムが便利な点は、新しいphpファイルを作成する場合に、sitemap.jsonにその情報を記述して、このページのアクセスするだけでphpファイルが生成されますので、ファイル作成時のタイプミスはありません。データと実ファイルの整合性も保たれます。一度にたくさんのphpファイルを追加作成する必要が出てきた際にはとても便利です。