Movable Type でエクスポートしたテーマの差分を効率的に比較する

Movable Type や PowerCMS(X を除く)で作成したテンプレートを「テーマ」としてエクスポートすると、デフォルトのファイル名は template_N.html(N は、テンプレート ID)となります。 この状態だと「どのファイルが、どのテンプレートに対応しているのか」が判断しづらいため、本番環境の最新データと手元のファイルとの差分を比較するのが手間・・・といった経験をお持ちの方は、少なからずいらっしゃることでしょう。

そこで、生成されるファイル名を制御できる「テンプレート識別子」を設定しておくと便利です。
Movable Type なら MTAppjQuery の導入、PowerCMS ならデバッグモードを有効にするだけで、テンプレートの編集画面で任意の「テンプレート識別子」をセットできるようになります。

MTAppjQuery でテンプレート識別子を設定可能にするには | プラグイン | Hei Blog
https://hei-a.net/blog/movable_type/plugin/template-identifier.html

テンプレートの識別子を変更したい | トラブルシューティング | ドキュメント | PowerCMS - カスタマイズする CMS
https://www.powercms.jp/products/document/troubleshooting/change-template-identifier.html

これで解決できればよいのですが、テーマを再適用したり、個別に新規テンプレートを作成したタイミングで意図せず template_N.mtml が生成されてしまうことがあります。開発フェーズであれば自由に調整できるものの、運用フェーズではすべてを管理するのが難しいこともしばしば。。。

そこで、ファイル名の整合性が取れない状態でエクスポートされた MTML ファイルを PHP でリネームする仕組みを考えてみました。

リネーム前の差分を比較

ここでは、次のようなケースを想定しています。

master と target ディレクトリのファイル構成の比較

  • 左:master ディレクトリ(比較元のテーマファイル)
  • 右:target ディレクトリ(リネームしたいテーマファイル)

それぞれ、エクスポート時の「出力ファイル名」は website_main です。master/website_main がテンプレート識別子をひと通り設定してある状態、target/website_main が本番環境などからエクスポートした際に「意図しないファイル名で出力されている」状態とします。

master と target それぞれの theme.yaml の比較

theme.yaml を比較すると、テンプレート名は同じでもテンプレート識別子が異なっていることがわかります。

なお、差分の比較は Beyond Compare がオススメですので、気になった方は過去記事もご覧ください。

事前準備

ファイル構成

作業用ディレクトリのファイル構成イメージは、次の通りです。
mastertarget ディレクトリ以外は、後続のステップで用意する Composer 関連のファイル・ディレクトリ、リネーム処理を行う PHP ファイルとなります。

./
├── composer.json
├── composer.lock
├── master/
│   └── website_main/
│       ├── templates/
│       │   ├── archive-entry.mtml
│       │   ├── index-top.mtml
│       │   ├── module-config.mtml
│       │   └── module-layout.mtml
│       └── theme.yaml
├── renameTemplate.php
├── target/
│   └── website_main/
│       ├── templates/
│       │   ├── module-config.mtml
│       │   ├── template_485.mtml
│       │   ├── template_54.mtml
│       │   └── template_917.mtml
│       └── theme.yaml
└── vendor/

まずは master 配下に「比較元のテーマファイル」、target 配下に「リネームしたいテーマファイル」を配置しておきましょう。

Composer パッケージのインストール

PHP で YAML の読み込みやパースを効率よく行うために、Spyc をインストールします。
作業用ディレクトリに移動し、次のコマンドを実行してください。

composer require mustangostang/spyc

なお、Symfony の Yaml Component も試してみたのですが、アーカイブマッピングに含まれる %b などのアーカイブファイル名の記述部分がパースエラーになるため、Spyc を採用しています。

リネームを実行する PHP ファイルを作成

続けて、作業用ディレクトリに renameTemplate.php を作成し、次の PHP コードを保存します。

<?php

/*
 * renameTemplate.php:テンプレートのリネーム
 * ----------------------------------------------
 * - ローカル環境でテーマ開発時に使用
 * - 機能
 *   - 「マスター」と「ターゲット」の theme.yaml を読み込み
 *   - 「マスター」と「ターゲット」の同一ラベルのテンプレート識別子を比較
 *   - 差分があれば、「ターゲット」のファイルをリネーム
 */

require 'vendor/autoload.php';

if(PHP_SAPI === 'cli') {
  // コマンドラインから実行された場合
  $theme_directory = (isset($argv) && count($argv) > 1) ? $argv[1] : '';
} else {
  // ブラウザから実行された場合
  $theme_directory = (isset($_GET['theme'])) ? $_GET['theme'] : '';
}

if($theme_directory === '') {
  echo 'テーマのディレクトリ名は必須です。' . PHP_EOL;
  return;
}

// theme.yaml からテンプレート識別子を取得
function getTemplateIdentifier($theme, $type = 'master') {
  $response = [];

  // $type が 'master' または 'target' 以外の場合は終了
  if(!in_array($type, ['master', 'target'])) {
    return $response;
  }

  // theme.yaml を読み込んで、配列に変換
  $yaml = file_get_contents($type . '/' . $theme . '/theme.yaml');
  $yaml = Spyc::YAMLLoad($yaml);

  // テンプレートのブロックをループ処理
  foreach ($yaml['elements']['template_set']['data']['templates'] as $templateType => $templates) {
    // system を除外
    if(!in_array($templateType, ['system'])) {
      foreach ($templates as $identifier => $template){
        $response[$template['label']] = $identifier;
      }
    }
  }

  return $response;
}

// マスター、ターゲットそれぞれのテンプレート識別子を取得
$master = getTemplateIdentifier($theme_directory);
$target = getTemplateIdentifier($theme_directory, 'target');

// $target のループ処理
foreach ($target as $label => $identifier) {
  // $master に存在しないテンプレートはスキップ
  if(!isset($master[$label])) {
    continue;
  }

  // $master と $target のテンプレート識別子が異なる場合
  if($master[$label] !== $identifier) {
    $rename_from = 'target/' . $theme_directory . '/templates/' . $identifier . '.mtml';
    $rename_to   = 'target/' . $theme_directory . '/templates/' . $master[$label] . '.mtml';

    if(file_exists($rename_from)) {
      // 対象ファイルが存在すれば、リネーム
      rename($rename_from, $rename_to);

      echo 'リネームしました:' . $identifier . '.mtml' . ' → ' . $master[$label] . '.mtml' . PHP_EOL;
    } else {
      echo '対象ファイルが存在しません:' . $identifier . '.mtml' . PHP_EOL;
    }

    // ブラウザから実行されていれば、改行タグを出力
    if(PHP_SAPI !== 'cli') {
      echo '<br>' . PHP_EOL;
    }
  }
}

ポイントを解説すると、実行時に指定されたテーマディレクトリ直下の theme.yaml を読み込み、$master$target にそれぞれのテンプレート名をキーとするテンプレート識別子の配列をセット。ループ処理でテンプレート名ごとのテンプレート識別子を比較し、同一でなければ $master の値に合わせてリネームを行います。

「とりあえず、目的が果たせれば...」くらいの軽い気持ちで作成していますので、あくまでもローカル環境での利用を想定しています。

リネーム処理の実行

コマンドラインから実行

作業用ディレクトリに移動し、次のコマンドを実行してください。 引数でテーマディレクトリ名を指定します。

php renameTemplate.php website_main

CLI での実行結果

処理が完了すると、リネームされたファイル名が出力されます。

ブラウザから実行

Docker などでローカル環境を構築している場合、ブラウザからアクセスしても実行できます。
URL パラメータ theme にテーマディレクトリ名を指定してください。

http://localhost/pathToDir/renameTemplate.php?theme=website_main

こちらも、リネームされたファイル名が出力されます。

実行後の差分比較

期待通りにリネームできたため、簡単に比較できるようになりました。

リネーム後の差分比較

あとは master 側に MTML の差分をマージしてから編集を加えれば、本番環境への反映もロールバックの心配が軽減できそうです。

まとめ

今回は、Movable Type や PowerCMS でエクスポートしたテーマの差分を効率的に比較するための、テーマファイルのリネーム方法についてご紹介しました。

そもそもとして、標準機能で「テンプレート識別子」が設定でき、かつ、テーマ適用時にインデックステンプレートの識別子をリセットしないようにして欲しいところですが、とりあえずはこの方法で対応できるかと思います。

という訳で、これは Movable Type Advent Calendar 2024 1日目の記事です。
ネタを探して彷徨ってたら、4年経ってました w