【PHP】あるディレクトリ以下の名前と中身中の特定の文字列を再帰的に全て置換するコード例

  • 2021年11月24日
  • 2021年11月25日
  • PHP

 何がしかのプロジェクトのテンプレートを使う時、特別親切な物は対話コンソールで特定の箇所に使われる文字列を質問して、それを自動で適切な場所に入れてくれるのですが、そうでないものも少なくないです。そういった時は次の様なスクリプトを使って、置き換えるべき文字列をまとめてガッと置き換えるのが楽です。

<?php

// このファイルと同じディレクトリにある test ディレクトリ以下の xxxx,Xxxx,XXXX を cpl, Cpl, CPL に置き換える例
main(__DIR__ . '/test', 'xxxx', 'cpl');

/**
 * あるディレクトリ以下に存在する文字列を全て置き換え
 * @param  string  $dir   置換対象のディレクトリをフルパス
 * @param  string  $src   置換したい文字列
 * @param  string  $dist  置換後の文字列
 */
function main(string $dir, string $src, string $dist): void
{
    echo "--replace contents--\n";
    replaceContents($dir, $src, $dist);
    echo "--replace paths--\n";
    replacePaths($dir, $src, $dist);
}

/**
 * あるディレクトリ以下に存在するファイル中の文字列を全て置き換え
 * @param  string  $dir   置換対象のディレクトリをフルパス
 * @param  string  $src   置換したい文字列
 * @param  string  $dist  置換後の文字列
 */
function replaceContents(string $dir, string $src, string $dist): void
{
    // あるディレクトリ以下のファイルとディレクトリのフルパスリストを用意
    $paths = getFileAndDirList($dir);
    // 全パスをループ
    foreach($paths as $filePath) {
        if(is_dir($filePath)) {
            continue; // ディレクトリは対象外なのでスキップ
        }
        // 変更があったか比較するために変数を二個用意 # str_replace で変数の中身を上書きしたせいで二個必要になったとも
        $old = $contents = file_get_contents($filePath);
        // 今回、これを作ったきっかけでは xxxx, Xxxx, XXXX を置換する必要があったので、大文字小文字の差異を吸収する置換
        // 純粋に $src から $dist に変更したいのであれば $contents = str_replace($src, $dist, $contents); にする
        $contents = str_replace(
            [strtolower($src), ucfirst($src), strtoupper($src)],
            [strtolower($dist), ucfirst($dist), strtoupper($dist)],
            $contents
        );
        // 変更があれば
        // - 変更したファイルのパスを画面に表示
        // - 置換した中身を書き込み
        if($old !== $contents) {
            echo $filePath . "\n";
            file_put_contents($filePath, $contents);
        }
    }
}

/**
 * あるディレクトリ以下に存在するファイルとディレクトリの名前を全て置き換え
 * @param  string  $dir   置換対象のディレクトリをフルパス
 * @param  string  $src   置換したい文字列
 * @param  string  $dist  置換後の文字列
 */
function replacePaths(string $dir, string $src, string $dist): void
{
    $moved     = true;// 移動があったか示すフラグ変数
    $loopCount = 0;// フェイルセーフ用。ループ回数が余りに多い場合は強制中断
    while($moved) {// 移動がなくなるまで無限ループ
        $moved = false;
        // あるディレクトリ以下のファイルとディレクトリのフルパスリストの最新を用意
        $paths = getFileAndDirList($dir);
        foreach($paths as $path) {
            // 今回、これを作ったきっかけでは xxxx, Xxxx, XXXX を置換する必要があったので、大文字小文字の差異を吸収する様に用意
            // 純粋に $src から $dist に変更したいのであればループをなしにして $strFn を取り除く
            foreach(['strtolower', 'ucfirst', 'strtoupper'] as $strFn) {
                // PHP では変数に関数名を入れて、その変数で関数呼び出しをすると入れた名前の関数が実行されます
                $to = str_replace($strFn($src), $strFn($dist), $path);
                if($path === $to) {
                    // 置き換えた結果のパスが同じであれば移動の必要がないのでスキップ
                    continue;
                }

                if(file_exists($to)) {
                    // 元々あった必要なファイルを壊すのが怖いので、移動先にファイルかディレクトリが既にある場合は無視
                    continue;
                }
                // ファイルパスを置き換え(移動)。
                // 冒頭で while するのはここの処理でしょっちゅう存在しないものを動かそうとするため
                rename($path, $to);
                // 移動を画面に通知
                echo $path . " to " . $to . "\n";
                $moved = true; // 移動があったことを示すフラグを立てる
            }
        }
        $loopCount++;
        if($loopCount > 100) {
            echo "過度なループによる強制終了";
            exit(1);
        }
    }
}

/**
 * あるディレクトリ以下のパスを全て集める関数
 * @see https://blog.asial.co.jp/1250
 * @param  string  $dir
 * @return array
 */
function getFileAndDirList(string $dir): array
{
    $files = scandir($dir);// あるディレクトリ直下のファイルとディレクトリの名前を全部集める
    // 自身と親のパスを弾く
    $files = array_filter($files, static fn($file) => !in_array($file, ['.', '..']));
    $paths  = [];
    foreach($files as $file) {
        // scandir で集めた名前をパスにして追加
        $fullPath = rtrim($dir, '/') . '/' . $file;
        $paths[]   = $fullPath;
        // 追加したパスがディレクトリなら再帰で更に中身を得る
        if(is_dir($fullPath)) {
            $paths = [...$paths, ...getFileAndDirList($fullPath)];
        }
    }
    return $paths;
}
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG