[PHP]runkit7_function_remove関数を完全解説!関数を削除する方法

PHP

こんにちは!今回は、PHPのrunkit7拡張機能で提供されるrunkit7_function_remove()関数について詳しく解説していきます。一度定義した関数を実行時に削除できる、特殊な関数です!

runkit7_function_remove関数とは?

runkit7_function_remove()関数は、既に定義されている関数を実行時に削除することができる関数です。

PHPでは通常、一度定義した関数は削除できませんが、この関数を使えば関数を「存在しなかったこと」にできます!

基本的な構文

runkit7_function_remove(string $funcname): bool
  • $funcname: 削除する関数の名前
  • 戻り値: 成功時にtrue、失敗時にfalse

前提条件

runkit7拡張機能のインストール

pecl install runkit7

php.iniに以下を追加:

extension=runkit7.so
runkit.internal_override=1

インストール確認:

<?php
if (function_exists('runkit7_function_remove')) {
    echo "runkit7が利用可能です!";
} else {
    echo "runkit7がインストールされていません";
}
?>

基本的な使用例

シンプルな関数の削除

// 関数を定義
function temporaryFunction() {
    return "一時的な関数";
}

echo temporaryFunction() . "\n"; // 出力: 一時的な関数

// 関数が存在することを確認
var_dump(function_exists('temporaryFunction')); // bool(true)

// 関数を削除
runkit7_function_remove('temporaryFunction');

// 削除されたことを確認
var_dump(function_exists('temporaryFunction')); // bool(false)

// 削除後は使用できない
// echo temporaryFunction(); // Fatal error: Call to undefined function

複数の関数を削除

// 複数の関数を定義
function func1() {
    return "関数1";
}

function func2() {
    return "関数2";
}

function func3() {
    return "関数3";
}

echo func1() . ", " . func2() . ", " . func3() . "\n";
// 出力: 関数1, 関数2, 関数3

// すべて削除
runkit7_function_remove('func1');
runkit7_function_remove('func2');
runkit7_function_remove('func3');

// すべて未定義になる
var_dump(function_exists('func1')); // bool(false)
var_dump(function_exists('func2')); // bool(false)
var_dump(function_exists('func3')); // bool(false)

実践的な使用例

例1: 一時的なヘルパー関数のクリーンアップ

// 処理の開始時に一時的なヘルパー関数を定義
function temp_generate_id() {
    return uniqid('temp_', true);
}

function temp_format_date($timestamp) {
    return date('Y-m-d H:i:s', $timestamp);
}

function temp_sanitize($input) {
    return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
}

// ヘルパー関数を使用
$id = temp_generate_id();
$date = temp_format_date(time());
$safe = temp_sanitize('<script>alert("xss")</script>');

echo "ID: {$id}\n";
echo "日付: {$date}\n";
echo "サニタイズ後: {$safe}\n";

// 処理完了後、一時関数をクリーンアップ
function cleanupTemporaryFunctions() {
    $tempFunctions = [
        'temp_generate_id',
        'temp_format_date',
        'temp_sanitize'
    ];
    
    foreach ($tempFunctions as $func) {
        if (function_exists($func)) {
            runkit7_function_remove($func);
            echo "削除しました: {$func}\n";
        }
    }
}

cleanupTemporaryFunctions();

// クリーンアップ後は未定義
var_dump(function_exists('temp_generate_id')); // bool(false)

例2: プラグインシステムでの動的な関数管理

class PluginManager {
    private $loadedPlugins = [];
    
    public function loadPlugin($name, $functions) {
        echo "プラグイン '{$name}' を読み込み中...\n";
        
        foreach ($functions as $funcName => $code) {
            list($args, $body) = $code;
            runkit7_function_add($funcName, $args, $body);
            
            if (!isset($this->loadedPlugins[$name])) {
                $this->loadedPlugins[$name] = [];
            }
            
            $this->loadedPlugins[$name][] = $funcName;
            echo "  関数 {$funcName} を登録\n";
        }
    }
    
    public function unloadPlugin($name) {
        if (!isset($this->loadedPlugins[$name])) {
            echo "プラグイン '{$name}' は読み込まれていません\n";
            return false;
        }
        
        echo "プラグイン '{$name}' をアンロード中...\n";
        
        foreach ($this->loadedPlugins[$name] as $funcName) {
            if (function_exists($funcName)) {
                runkit7_function_remove($funcName);
                echo "  関数 {$funcName} を削除\n";
            }
        }
        
        unset($this->loadedPlugins[$name]);
        return true;
    }
    
    public function listLoadedPlugins() {
        echo "=== 読み込まれているプラグイン ===\n";
        foreach ($this->loadedPlugins as $name => $functions) {
            echo "{$name}: " . implode(', ', $functions) . "\n";
        }
    }
}

// 使用例
$manager = new PluginManager();

// プラグイン1を読み込み
$manager->loadPlugin('ImagePlugin', [
    'plugin_resize_image' => ['$width, $height', 'return "画像をリサイズ: {$width}x{$height}";'],
    'plugin_crop_image' => ['$x, $y, $w, $h', 'return "画像をクロップ";']
]);

// プラグイン2を読み込み
$manager->loadPlugin('TextPlugin', [
    'plugin_uppercase_text' => ['$text', 'return strtoupper($text);'],
    'plugin_reverse_text' => ['$text', 'return strrev($text);']
]);

$manager->listLoadedPlugins();

// プラグインの関数を使用
echo plugin_resize_image(100, 200) . "\n";
echo plugin_uppercase_text('hello') . "\n";

// プラグイン1をアンロード
$manager->unloadPlugin('ImagePlugin');

// アンロード後は使用不可
var_dump(function_exists('plugin_resize_image')); // bool(false)
var_dump(function_exists('plugin_uppercase_text')); // bool(true) - まだ存在

例3: テストケース間のクリーンアップ

class TestRunner {
    private $testFunctions = [];
    
    public function setUp($testName) {
        echo "\n=== テストケース: {$testName} ===\n";
        $this->testFunctions = [];
    }
    
    public function addTestHelper($funcName, $args, $code) {
        runkit7_function_add($funcName, $args, $code);
        $this->testFunctions[] = $funcName;
        echo "テストヘルパー追加: {$funcName}\n";
    }
    
    public function tearDown() {
        echo "テストのクリーンアップ中...\n";
        
        foreach ($this->testFunctions as $func) {
            if (function_exists($func)) {
                runkit7_function_remove($func);
                echo "  {$func} を削除\n";
            }
        }
        
        $this->testFunctions = [];
    }
    
    public function run($testName, $testCode) {
        $this->setUp($testName);
        
        try {
            $testCode();
            echo "✓ テスト成功\n";
        } catch (Exception $e) {
            echo "✗ テスト失敗: " . $e->getMessage() . "\n";
        }
        
        $this->tearDown();
    }
}

// 使用例
$runner = new TestRunner();

// テスト1
$runner->run('ユーザー作成テスト', function() use ($runner) {
    $runner->addTestHelper(
        'test_create_user',
        '$name',
        'return ["id" => 1, "name" => $name, "status" => "active"];'
    );
    
    $user = test_create_user('田中');
    assert($user['name'] === '田中');
    echo "ユーザー作成成功: {$user['name']}\n";
});

// テスト2(前のテストの関数は削除されている)
$runner->run('データ処理テスト', function() use ($runner) {
    // 前のtest_create_user関数は存在しない
    if (function_exists('test_create_user')) {
        throw new Exception('前のテストの関数が残っています!');
    }
    
    $runner->addTestHelper(
        'test_process_data',
        '$data',
        'return strtoupper($data);'
    );
    
    $result = test_process_data('hello');
    assert($result === 'HELLO');
    echo "データ処理成功: {$result}\n";
});

例4: デバッグ関数の動的管理

class DebugManager {
    private static $debugFunctions = [];
    private static $isEnabled = false;
    
    public static function enable() {
        if (self::$isEnabled) {
            echo "デバッグモードは既に有効です\n";
            return;
        }
        
        echo "デバッグモードを有効化しています...\n";
        
        // デバッグ関数を追加
        runkit7_function_add(
            'dd',
            '...$vars',
            '
                foreach ($vars as $var) {
                    var_dump($var);
                }
                die("デバッグ停止");
            '
        );
        self::$debugFunctions[] = 'dd';
        
        runkit7_function_add(
            'debug_trace',
            '',
            '
                $trace = debug_backtrace();
                echo "=== スタックトレース ===\n";
                foreach ($trace as $i => $t) {
                    echo "#{$i} ";
                    if (isset($t["file"])) echo $t["file"] . ":" . $t["line"];
                    if (isset($t["function"])) echo " " . $t["function"] . "()";
                    echo "\n";
                }
            '
        );
        self::$debugFunctions[] = 'debug_trace';
        
        runkit7_function_add(
            'debug_log',
            '$message',
            '
                $timestamp = date("Y-m-d H:i:s");
                echo "[DEBUG {$timestamp}] {$message}\n";
            '
        );
        self::$debugFunctions[] = 'debug_log';
        
        self::$isEnabled = true;
        echo "デバッグ関数を追加しました: " . implode(', ', self::$debugFunctions) . "\n";
    }
    
    public static function disable() {
        if (!self::$isEnabled) {
            echo "デバッグモードは既に無効です\n";
            return;
        }
        
        echo "デバッグモードを無効化しています...\n";
        
        foreach (self::$debugFunctions as $func) {
            if (function_exists($func)) {
                runkit7_function_remove($func);
                echo "  {$func} を削除\n";
            }
        }
        
        self::$debugFunctions = [];
        self::$isEnabled = false;
    }
}

// 使用例
echo "通常モードで実行中...\n";
// debug_log('test'); // エラー: 関数が存在しない

DebugManager::enable();

// デバッグ関数が使用可能に
debug_log('アプリケーション開始');
debug_log('データ処理中');
debug_trace();

// 本番モードに戻す
DebugManager::disable();

// 再びデバッグ関数は使用不可
var_dump(function_exists('dd')); // bool(false)

例5: セキュリティ上危険な関数の無効化

class SecurityManager {
    private static $disabledFunctions = [];
    
    public static function disableFunction($funcName) {
        if (!function_exists($funcName)) {
            echo "警告: 関数 {$funcName} は存在しません\n";
            return false;
        }
        
        // 関数をバックアップ
        $backupName = $funcName . '_disabled_backup';
        runkit7_function_copy($funcName, $backupName);
        
        // 警告を出す関数に置き換え
        runkit7_function_redefine(
            $funcName,
            '...$args',
            '
                trigger_error("セキュリティ上の理由により、' . $funcName . '()は無効化されています", E_USER_WARNING);
                return false;
            '
        );
        
        self::$disabledFunctions[$funcName] = $backupName;
        echo "関数 {$funcName} を無効化しました\n";
        
        return true;
    }
    
    public static function removeFunction($funcName) {
        if (!function_exists($funcName)) {
            echo "警告: 関数 {$funcName} は存在しません\n";
            return false;
        }
        
        // 完全に削除
        runkit7_function_remove($funcName);
        echo "関数 {$funcName} を完全に削除しました\n";
        
        return true;
    }
    
    public static function restoreFunction($funcName) {
        if (!isset(self::$disabledFunctions[$funcName])) {
            echo "エラー: {$funcName} は無効化されていません\n";
            return false;
        }
        
        $backupName = self::$disabledFunctions[$funcName];
        
        // 元に戻す
        runkit7_function_remove($funcName);
        runkit7_function_rename($backupName, $funcName);
        
        unset(self::$disabledFunctions[$funcName]);
        echo "関数 {$funcName} を復元しました\n";
        
        return true;
    }
}

// テスト用の危険な関数を定義
function dangerousOperation($data) {
    return "危険な操作: {$data}";
}

echo dangerousOperation('test') . "\n"; // 出力: 危険な操作: test

// セキュリティモードで無効化
SecurityManager::disableFunction('dangerousOperation');

// 使用しようとすると警告が出る
dangerousOperation('test'); // Warning: セキュリティ上の理由により...

// 復元
SecurityManager::restoreFunction('dangerousOperation');
echo dangerousOperation('test') . "\n"; // 出力: 危険な操作: test

例6: 機能フラグによる関数の有効/無効化

class FeatureManager {
    private static $features = [];
    
    public static function registerFeature($featureName, $functions) {
        self::$features[$featureName] = [
            'enabled' => false,
            'functions' => $functions
        ];
    }
    
    public static function enableFeature($featureName) {
        if (!isset(self::$features[$featureName])) {
            echo "エラー: 機能 {$featureName} は登録されていません\n";
            return false;
        }
        
        if (self::$features[$featureName]['enabled']) {
            echo "機能 {$featureName} は既に有効です\n";
            return true;
        }
        
        echo "機能 {$featureName} を有効化しています...\n";
        
        foreach (self::$features[$featureName]['functions'] as $func) {
            list($name, $args, $code) = $func;
            runkit7_function_add($name, $args, $code);
            echo "  関数 {$name} を追加\n";
        }
        
        self::$features[$featureName]['enabled'] = true;
        return true;
    }
    
    public static function disableFeature($featureName) {
        if (!isset(self::$features[$featureName])) {
            echo "エラー: 機能 {$featureName} は登録されていません\n";
            return false;
        }
        
        if (!self::$features[$featureName]['enabled']) {
            echo "機能 {$featureName} は既に無効です\n";
            return true;
        }
        
        echo "機能 {$featureName} を無効化しています...\n";
        
        foreach (self::$features[$featureName]['functions'] as $func) {
            $name = $func[0];
            if (function_exists($name)) {
                runkit7_function_remove($name);
                echo "  関数 {$name} を削除\n";
            }
        }
        
        self::$features[$featureName]['enabled'] = false;
        return true;
    }
    
    public static function listFeatures() {
        echo "=== 登録されている機能 ===\n";
        foreach (self::$features as $name => $info) {
            $status = $info['enabled'] ? '有効' : '無効';
            $funcCount = count($info['functions']);
            echo "{$name}: {$status} ({$funcCount}個の関数)\n";
        }
    }
}

// 機能を登録
FeatureManager::registerFeature('BetaUI', [
    ['show_beta_banner', '', 'return "<div class=\"beta-banner\">ベータ版機能</div>";'],
    ['get_beta_features', '', 'return ["feature1", "feature2", "feature3"];']
]);

FeatureManager::registerFeature('AdvancedAnalytics', [
    ['track_event', '$event', 'return "イベント追跡: {$event}";'],
    ['get_analytics_data', '', 'return ["visits" => 1000, "conversions" => 50];']
]);

FeatureManager::listFeatures();

// ベータUIを有効化
FeatureManager::enableFeature('BetaUI');

echo show_beta_banner() . "\n";
print_r(get_beta_features());

// ベータUIを無効化
FeatureManager::disableFeature('BetaUI');

var_dump(function_exists('show_beta_banner')); // bool(false)

重要な注意点と制限事項

1. PHP組み込み関数は削除できない

// PHP組み込み関数の削除は失敗する
$result = runkit7_function_remove('strlen');
var_dump($result); // bool(false)

echo strlen('test'); // 出力: 4 (削除されない)

// 同様に失敗する例
// runkit7_function_remove('array_map');   // 失敗
// runkit7_function_remove('json_encode'); // 失敗
// runkit7_function_remove('file_exists'); // 失敗

2. 存在しない関数の削除

// 存在しない関数を削除しようとすると失敗
$result = runkit7_function_remove('nonExistentFunction');
var_dump($result); // bool(false)

3. 削除後の関数呼び出し

function myFunction() {
    return "test";
}

// 関数への参照を保持
$func = 'myFunction';

echo $func() . "\n"; // 出力: test

// 関数を削除
runkit7_function_remove('myFunction');

// 削除後は呼び出せない
// echo $func(); // Fatal error: Call to undefined function myFunction()
// echo myFunction(); // Fatal error

4. クロージャ内で使用されている場合

function helperFunction() {
    return "helper";
}

$closure = function() {
    return "Result: " . helperFunction();
};

echo $closure() . "\n"; // 出力: Result: helper

// ヘルパー関数を削除
runkit7_function_remove('helperFunction');

// クロージャは実行時エラーになる
// echo $closure(); // Fatal error: Call to undefined function helperFunction()

エラーハンドリングとベストプラクティス

安全な削除関数の実装

function safeRemoveFunction($funcName) {
    // 関数の存在確認
    if (!function_exists($funcName)) {
        echo "警告: 関数 {$funcName} は存在しません\n";
        return false;
    }
    
    // PHP組み込み関数かチェック
    $reflection = new ReflectionFunction($funcName);
    if ($reflection->isInternal()) {
        echo "エラー: {$funcName} はPHP組み込み関数なので削除できません\n";
        return false;
    }
    
    // 削除前の情報を記録
    $params = [];
    foreach ($reflection->getParameters() as $param) {
        $params[] = '$' . $param->getName();
    }
    echo "削除: {$funcName}(" . implode(', ', $params) . ")\n";
    
    // 削除実行
    $result = runkit7_function_remove($funcName);
    
    if ($result) {
        echo "成功: {$funcName} を削除しました\n";
    } else {
        echo "エラー: {$funcName} の削除に失敗しました\n";
    }
    
    return $result;
}

// 使用例
function testFunction($x, $y) {
    return $x + $y;
}

safeRemoveFunction('testFunction');        // 成功
safeRemoveFunction('strlen');              // エラー: 組み込み関数
safeRemoveFunction('nonExistent');         // 警告: 存在しない

関数の削除履歴管理

class FunctionRemovalTracker {
    private static $removedFunctions = [];
    
    public static function remove($funcName, $reason = '') {
        if (!function_exists($funcName)) {
            return false;
        }
        
        // 関数情報を記録
        $reflection = new ReflectionFunction($funcName);
        $info = [
            'name' => $funcName,
            'file' => $reflection->getFileName(),
            'line' => $reflection->getStartLine(),
            'reason' => $reason,
            'removed_at' => date('Y-m-d H:i:s')
        ];
        
        // 削除実行
        $result = runkit7_function_remove($funcName);
        
        if ($result) {
            self::$removedFunctions[] = $info;
            echo "削除: {$funcName}";
            if ($reason) echo " (理由: {$reason})";
            echo "\n";
        }
        
        return $result;
    }
    
    public static function getHistory() {
        return self::$removedFunctions;
    }
    
    public static function showHistory() {
        echo "=== 削除された関数の履歴 ===\n";
        foreach (self::$removedFunctions as $info) {
            echo "[{$info['removed_at']}] {$info['name']}";
            if ($info['reason']) echo " - {$info['reason']}";
            echo "\n";
            if ($info['file']) {
                echo "  定義場所: {$info['file']}:{$info['line']}\n";
            }
        }
    }
}

// 使用例
function oldFunction1() { return "old1"; }
function oldFunction2() { return "old2"; }
function tempFunction() { return "temp"; }

FunctionRemovalTracker::remove('oldFunction1', '非推奨のため');
FunctionRemovalTracker::remove('oldFunction2', 'バグがあるため');
FunctionRemovalTracker::remove('tempFunction', '一時的な関数');

FunctionRemovalTracker::showHistory();

一括削除機能

/**
 * プレフィックスに一致する関数を一括削除
 */
function removeFunctionsByPrefix($prefix) {
    $allFunctions = get_defined_functions();
    $userFunctions = $allFunctions['user'];
    
    $removed = [];
    
    foreach ($userFunctions as $funcName) {
        if (strpos($funcName, $prefix) === 0) {
            if (runkit7_function_remove($funcName)) {
                $removed[] = $funcName;
            }
        }
    }
    
    return $removed;
}

// 使用例
function test_function_1() { return 1; }
function test_function_2() { return 2; }
function test_function_3() { return 3; }
function other_function() { return 'other'; }

echo "削除前:\n";
var_dump(function_exists('test_function_1')); // bool(true)
var_dump(function_exists('other_function'));  // bool(true)

// test_で始まる関数をすべて削除
$removed = removeFunctionsByPrefix('test_');

echo "\n削除された関数:\n";
print_r($removed);
// 出力: Array ( [0] => test_function_1 [1] => test_function_2 [2] => test_function_3 )

echo "\n削除後:\n";
var_dump(function_exists('test_function_1')); // bool(false)
var_dump(function_exists('other_function'));  // bool(true) - 残る

パフォーマンスへの影響

// パフォーマンステスト
function performanceTest() {
    // 1000個の関数を作成
    $start = microtime(true);
    for ($i = 1; $i <= 1000; $i++) {
        runkit7_function_add("perf_func_{$i}", '', 'return ' . $i . ';');
    }
    $createTime = microtime(true) - $start;
    
    echo "1000個の関数作成: {$createTime}秒\n";
    
    // すべて削除
    $start = microtime(true);
    for ($i = 1; $i <= 1000; $i++) {
        runkit7_function_remove("perf_func_{$i}");
    }
    $removeTime = microtime(true) - $start;
    
    echo "1000個の関数削除: {$removeTime}秒\n";
    
    // 確認
    $stillExists = 0;
    for ($i = 1; $i <= 1000; $i++) {
        if (function_exists("perf_func_{$i}")) {
            $stillExists++;
        }
    }
    
    echo "削除後に残っている関数: {$stillExists}個\n";
}

performanceTest();

より安全な代替手段

実際の開発では、以下の方法を検討することをお勧めします:

1. 変数のunsetを使用

// 関数の代わりにクロージャを使用
$temporaryFunction = function($x) {
    return $x * 2;
};

echo $temporaryFunction(5) . "\n"; // 出力: 10

// 不要になったら削除
unset($temporaryFunction);

// 未定義になる
var_dump(isset($temporaryFunction)); // bool(false)

2. スコープを利用した自動クリーンアップ

function processWithTemporaryHelpers() {
    // この関数内でのみ有効なヘルパー
    $helper1 = function($x) {
        return $x * 2;
    };
    
    $helper2 = function($x) {
        return $x + 10;
    };
    
    // ヘルパーを使用
    $result = $helper2($helper1(5));
    echo "結果: {$result}\n"; // 出力: 20
    
    // 関数終了時に自動的にクリーンアップされる
}

processWithTemporaryHelpers();

// 関数外ではアクセスできない

3. クラスメソッドでの管理

class TemporaryFunctions {
    private static $functions = [];
    
    public static function add($name, $callback) {
        self::$functions[$name] = $callback;
    }
    
    public static function call($name, ...$args) {
        if (!isset(self::$functions[$name])) {
            throw new Exception("Function {$name} not found");
        }
        
        return call_user_func_array(self::$functions[$name], $args);
    }
    
    public static function remove($name) {
        unset(self::$functions[$name]);
    }
    
    public static function clear() {
        self::$functions = [];
    }
}

// 使用例
TemporaryFunctions::add('double', fn($x) => $x * 2);
TemporaryFunctions::add('triple', fn($x) => $x * 3);

echo TemporaryFunctions::call('double', 5) . "\n"; // 出力: 10
echo TemporaryFunctions::call('triple', 5) . "\n"; // 出力: 15

// 削除
TemporaryFunctions::remove('double');

// すべてクリア
TemporaryFunctions::clear();

4. レジストリパターン

class FunctionRegistry {
    private static $registry = [];
    
    public static function register($name, callable $function) {
        self::$registry[$name] = $function;
    }
    
    public static function execute($name, ...$args) {
        if (!isset(self::$registry[$name])) {
            throw new Exception("Function {$name} is not registered");
        }
        
        return (self::$registry[$name])(...$args);
    }
    
    public static function unregister($name) {
        unset(self::$registry[$name]);
    }
    
    public static function isRegistered($name) {
        return isset(self::$registry[$name]);
    }
}

// 使用例
FunctionRegistry::register('greet', function($name) {
    return "Hello, {$name}!";
});

echo FunctionRegistry::execute('greet', 'Alice') . "\n"; // 出力: Hello, Alice!

FunctionRegistry::unregister('greet');

var_dump(FunctionRegistry::isRegistered('greet')); // bool(false)

まとめ

runkit7_function_remove()関数の特徴をまとめると:

できること:

  • 既存の関数を実行時に削除
  • 一時的なヘルパー関数のクリーンアップ
  • プラグインシステムでの動的な関数管理
  • テスト環境での関数の分離

注意点:

  • runkit7拡張機能のインストールが必要
  • PHP組み込み関数は削除できない
  • 存在しない関数は削除できない
  • 削除後の関数呼び出しに注意が必要

推奨される使用場面:

  • 一時的な関数のクリーンアップ
  • テスト環境での関数の分離
  • プラグインシステムの実装
  • デバッグ関数の動的管理

より良い代替手段:

  • クロージャとunset
  • スコープを利用した自動クリーンアップ
  • クラスメソッドでの管理
  • レジストリパターン

runkit7_function_remove()は特定の状況では便利ですが、通常のアプリケーション開発ではクロージャやクラスメソッドを使った方がシンプルで安全です。関数の削除はコードの予測可能性を損なう可能性があるため、特別な理由がない限り標準的な方法を使うことをお勧めします!

タイトルとURLをコピーしました