تابع register_deactivation_hook() در وردپرس نقطه ورود رسمی برای اجرای کد در زمان غیرفعال‌سازی یک افزونه است و به‌عنوان یکی از پایه‌ای‌ترین توابع چرخه عمر افزونه، امکان پاک‌سازی cron jobها، flush rewrite rules و پاک کردن داده‌های موقت را فراهم می‌کند. بدون این تابع، افزونه بعد از غیرفعال‌سازی، ردپای عملکردی خود را در سایت باقی می‌گذارد.

تابع register_deactivation_hook وردپرس یکی از پرکاربردترین توابع چرخه عمر افزونه برای اجرای کد در زمان غیرفعال‌سازی است. این تابع امکان پاک‌سازی cron job، flush rewrite rules، حذف داده‌های موقت و حفظ داده‌های کاربر را فراهم می‌کند و پایه مدیریت حرفه‌ای چرخه عمر افزونه محسوب می‌شود. در این راهنما ساختار کامل، پارامترها، نمونه‌های واقعی، اشتباهات رایج و نکات عملکردی این تابع بررسی می‌شود. همچنین تفاوت آن با register_activation_hook توضیح داده می‌شود. در پایان پرسش‌های پرتکرار و نگاه فنی عمیق به این تابع مرور خواهد شد.

در پروژه‌هایی که افزونه‌ها cron job یا post type سفارشی داشتند، نبود این hook همیشه به مشکلات پنهان منجر می‌شد. cron jobهایی که در زمان غیرفعال‌سازی پاک نمی‌شوند، همچنان در دیتابیس باقی می‌مانند و در صورت نبود افزونه، به خطاهای PHP منجر می‌شوند.

چرا register_deactivation_hook اهمیت دارد

غیرفعال‌سازی افزونه در وردپرس یک عملیات پیچیده‌تر از آن چیزی است که در نگاه اول به‌نظر می‌رسد. این عملیات نباید داده‌های کاربر را حذف کند، اما باید تمام رخدادهای موقت و ساختارهای اجرایی را پاک کند. تشخیص مرز بین «داده کاربر» و «داده موقت» نیازمند درک دقیق این مرحله است.

تابع register_deactivation_hook() به‌طور مشخص برای این مرحله طراحی شده است. وظیفه این hook:

  • پاک‌سازی cron jobهای ثبت‌شده
  • flush کردن rewrite rules
  • حذف Transientها
  • پاک کردن Object Cache
  • بستن اتصال‌های فعال به سرویس‌های خارجی

نکته مهم: این hook نباید داده‌های کاربر را حذف کند. حذف داده‌ها باید در uninstall.php یا hook uninstall انجام شود. برای درک کامل چرخه عمر، مطالب تابع register_activation_hook و تابع register_deactivation_hook را مطالعه کنید.

ساختار و امضای تابع register_deactivation_hook

امضای این تابع به شکل زیر است:

register_deactivation_hook( string $file, callable $callback ): void

پارامتر اول مسیر فایل اصلی افزونه است که معمولاً با __FILE__ پاس داده می‌شود. پارامتر دوم callbackی است که در زمان غیرفعال‌سازی اجرا می‌شود. خروجی این تابع void است.

مثال عملی:

register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

function myplugin_deactivate() {
    // کد اجرا شده در زمان غیرفعال‌سازی
}

پارامترها و کاربرد __FILE__

مهم‌ترین نکته در مورد این تابع، دقت در استفاده از __FILE__ است. اگر به‌جای __FILE__ یک مسیر دستی پاس دهید، hook در زمان غیرفعال‌سازی اجرا نمی‌شود.

// درست
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

// اشتباه، مسیر دستی
register_deactivation_hook( '/plugins/my-plugin/my-plugin.php', 'myplugin_deactivate' );

مقدار __FILE__ در هر فایل، مسیر کامل آن فایل است. توصیه استاندارد این است که register_deactivation_hook در فایل اصلی افزونه فراخوانی شود، نه در فایل‌های داخلی.

کاربردهای رایج در پروژه واقعی

پاک‌سازی cron jobها

رایج‌ترین کاربرد این تابع، پاک کردن cron jobهایی است که در فعال‌سازی ثبت شده‌اند:

function myplugin_deactivate() {
    wp_clear_scheduled_hook( 'myplugin_daily_cleanup' );
    wp_clear_scheduled_hook( 'myplugin_hourly_sync' );
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

بدون این پاک‌سازی، cron jobها در دیتابیس باقی می‌مانند و در بارگذاری‌های بعدی، وردپرس تلاش می‌کند آن‌ها را اجرا کند که به خطا منجر می‌شود. مطلب تابع wp_clear_scheduled_hook راهنمای کامل است.

پاک‌سازی rewrite rules

اگر افزونه post type سفارشی داشت، باید در زمان غیرفعال‌سازی، rewrite rules را پاک کند:

function myplugin_deactivate() {
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

این کار باعث می‌شود قوانین rewrite مربوط به post type حذف شوند و در بارگذاری بعدی، سایت دچار 404 برای URLهای قبلی نشود. برای مطالعه بیشتر، مطلب تابع flush_rewrite_rules را ببینید.

حذف Transientها

Transientها داده‌های موقت هستند که اگر پاک نشوند، در دیتابیس باقی می‌مانند و می‌توانند در آینده به رفتار غیرمنتظره منجر شوند:

function myplugin_deactivate() {
    delete_transient( 'myplugin_remote_data' );
    delete_transient( 'myplugin_api_response' );
    delete_transient( 'myplugin_cache_key' );
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

برای مطالعه دقیق Transient، مطلب تابع delete_transient راهنماست.

پاک کردن Object Cache

اگر افزونه از Object Cache استفاده می‌کند، در غیرفعال‌سازی باید کش مربوطه را پاک کند:

function myplugin_deactivate() {
    wp_cache_delete( 'myplugin_data', 'myplugin' );
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

مطلب تابع wp_cache_delete راهنماست.

بستن اتصال به سرویس‌های خارجی

function myplugin_deactivate() {
    $api = new MyPlugin_Remote_API();
    $api->close_connection();
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

پاک‌سازی فایل‌های Cache

اگر افزونه فایل‌های cache روی دیسک ساخته، در غیرفعال‌سازی می‌توانید آن‌ها را پاک کنید:

function myplugin_deactivate() {
    $cache_dir = WP_CONTENT_DIR . '/cache/my-plugin';
    if ( is_dir( $cache_dir ) ) {
        $files = glob( $cache_dir . '/*' );
        foreach ( $files as $file ) {
            if ( is_file( $file ) ) {
                unlink( $file );
            }
        }
    }
}

توجه: این کار باید با احتیاط انجام شود تا فایل‌های مهم حذف نشوند.

نمونه‌های عملی در پروژه واقعی

افزونه کامل با غیرفعال‌سازی

// my-plugin.php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'MYPLUGIN_DIR', plugin_dir_path( __FILE__ ) );

require_once MYPLUGIN_DIR . 'includes/class-installer.php';

register_activation_hook( __FILE__, array( 'MyPlugin_Installer', 'activate' ) );
register_deactivation_hook( __FILE__, array( 'MyPlugin_Installer', 'deactivate' ) );

کلاس Installer با متد deactivate

class MyPlugin_Installer {

    public static function deactivate() {
        self::clear_scheduled_events();
        self::clear_transients();
        self::clear_cache();
        flush_rewrite_rules();
    }

    private static function clear_scheduled_events() {
        wp_clear_scheduled_hook( 'myplugin_daily_cleanup' );
        wp_clear_scheduled_hook( 'myplugin_hourly_sync' );
    }

    private static function clear_transients() {
        global $wpdb;
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                '_transient_myplugin_%',
                '_transient_timeout_myplugin_%'
            )
        );
    }

    private static function clear_cache() {
        wp_cache_delete( 'myplugin_data', 'myplugin' );
    }
}

در این الگو، پاک‌سازی Transientها با کوئری مستقیم انجام می‌شود. برای مطالعه امنیت کوئری، مطلب SQL Injection Prevention در وردپرس را ببینید.

حفظ داده‌های کاربر

در غیرفعال‌سازی، نباید داده‌های کاربر حذف شوند. اگر می‌خواهید داده‌ها هم پاک شوند، از uninstall.php استفاده کنید:

// uninstall.php
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    exit;
}

delete_option( 'myplugin_settings' );
// پاک‌سازی سایر داده‌های کاربر

بررسی نسخه قبل از غیرفعال‌سازی

public static function deactivate() {
    $version = get_option( 'myplugin_version' );
    if ( version_compare( $version, '2.0.0', '>=' ) ) {
        self::run_v2_cleanup();
    }
    self::clear_scheduled_events();
    flush_rewrite_rules();
}

نمایش پیام پس از غیرفعال‌سازی

public static function deactivate() {
    set_transient( 'myplugin_deactivated_notice', true, 60 );
    flush_rewrite_rules();
}

add_action( 'admin_notices', function () {
    if ( get_transient( 'myplugin_deactivated_notice' ) ) {
        delete_transient( 'myplugin_deactivated_notice' );
        printf(
            '<div class="notice notice-success is-dismissible"><p>%s</p></div>',
            esc_html__( 'افزونه با موفقیت غیرفعال شد.', 'my-plugin' )
        );
    }
} );

غیرفعال‌سازی از طریق WP-CLI

wp plugin deactivate my-plugin

مطلب راهنمای WP-CLI الگوهای این کار را پوشش می‌دهد.

اشتباهات رایج در استفاده از register_deactivation_hook

نبود پاک‌سازی cron job

شایع‌ترین اشتباه. اگر cron jobها در غیرفعال‌سازی پاک نشوند، در دیتابیس باقی می‌مانند و در بارگذاری‌های بعدی، وردپرس تلاش می‌کند آن‌ها را اجرا کند. اگر تابع callback دیگر وجود نداشته باشد، خطای PHP رخ می‌دهد.

نبود flush rewrite rules

اگر افزونه post type سفارشی داشت، بدون flush در زمان غیرفعال‌سازی، URLهای post type به 404 تبدیل می‌شوند و رفتار غیرمنتظره رخ می‌دهد.

حذف داده‌های کاربر

اشتباه بحرانی. اگر در غیرفعال‌سازی، تنظیمات یا داده‌های کاربر حذف شود و کاربر بعداً افزونه را مجدداً فعال کند، داده‌ها از دست رفته‌اند. حذف داده‌ها فقط در uninstall.php.

نبود بررسی خروجی و خطا

در متد deactivate، خروجی HTML یا echo اضافه نکنید چون در فرآیند غیرفعال‌سازی، خروجی‌های اضافه به خطای headers already sent منجر می‌شوند.

نبود بررسی در کلاس‌های استاتیک

اگر callback شما یک متد استاتیک است، همیشه با class_exists بررسی کنید که کلاس وجود دارد قبل از ثبت hook:

if ( class_exists( 'MyPlugin_Installer' ) ) {
    register_deactivation_hook( __FILE__, array( 'MyPlugin_Installer', 'deactivate' ) );
}

نبود پاک‌سازی منابع خارجی

اگر افزونه به سرویس خارجی متصل است، در غیرفعال‌سازی باید اتصال را ببندد یا به سرویس اطلاع دهد. بدون این کار، ممکن است سرویس خارجی منابع را همچنان مصرف کند.

نبود تست روی سناریوهای مرزی

تست‌هایی مثل «غیرفعال‌سازی بدون cron»، «غیرفعال‌سازی بدون post type»، «غیرفعال‌سازی روی سایت با داده حجیم» و «غیرفعال‌سازی سریع» را حتماً بنویسید.

امنیت و عملکرد در register_deactivation_hook

این تابع به‌تنهایی امنیت را تهدید نمی‌کند، اما callback آن در محیط حساس غیرفعال‌سازی اجرا می‌شود. نکات مهم:

  • داده‌های کاربر را حذف نکنید
  • خروجی HTML اضافه نکنید
  • عملیات طولانی را به‌صورت async انجام دهید
  • در لاگ‌ها اطلاعات حساس ثبت نکنید
  • در پاک‌سازی دیتابیس، از prepared statement استفاده کنید

برای مطالعه جامع مباحث امنیتی، مطلب SQL Injection Prevention در وردپرس و راهنمای Sanitization مرجع هستند.

از نظر عملکرد، این تابع فقط یک بار در زمان غیرفعال‌سازی اجرا می‌شود و هزینه‌ای در بارگذاری روزانه سایت ندارد. اما عملیات داخل آن می‌تواند سنگین باشد:

  1. پاک‌سازی تعداد زیادی cron job می‌تواند کند باشد
  2. flush rewrite rules یک کوئری سنگین است
  3. پاک‌سازی Transientها می‌تواند روی دیتابیس‌های بزرگ کند باشد
  4. پاک کردن فایل‌های cache روی دیسک می‌تواند کند باشد

توصیه می‌شود عملیات سنگین را با محدودیت تعداد انجام دهید یا از پس‌زمینه با تابع wp_schedule_single_event استفاده کنید.

پرسش‌های پرتکرار درباره register_deactivation_hook

تفاوت register_deactivation_hook با register_activation_hook چیست؟

register_deactivation_hook() کد را در زمان غیرفعال‌سازی افزونه اجرا می‌کند و برای پاک‌سازی استفاده می‌شود، در حالی که register_activation_hook() در زمان فعال‌سازی برای راه‌اندازی اجرا می‌شود.

آیا در غیرفعال‌سازی می‌توان داده‌های کاربر را حذف کرد؟

خیر، توصیه نمی‌شود. حذف داده‌ها باید در uninstall.php یا hook uninstall انجام شود چون کاربر ممکن است بخواهد افزونه را مجدداً فعال کند.

آیا این تابع در هر بار بارگذاری سایت اجرا می‌شود؟

خیر، فقط یک بار در زمان غیرفعال‌سازی افزونه.

آیا می‌توان چند callback ثبت کرد؟

خیر، هر افزونه فقط یک deactivation hook دارد. اگر چند تابع نیاز دارید، آن‌ها را در یک callback مرکزی فراخوانی کنید.

آیا در غیرفعال‌سازی می‌توان خروجی به کاربر نشان داد؟

خیر، در فرآیند غیرفعال‌سازی نمی‌توانید خروجی HTML اضافه کنید. برای نمایش پیام، از Transient استفاده کنید و در بارگذاری بعدی پیشخوان، پیام را نمایش دهید.

آیا این تابع در Multisite رفتار خاصی دارد؟

در Multisite، اگر افزونه در سطح شبکه غیرفعال شود، hook یک بار اجرا می‌شود. اگر برای هر سایت جداگانه غیرفعال شود، برای هر سایت اجرا می‌شود. برای مطالعه بیشتر، مطلب مدیریت Multisite وردپرس را ببینید.

آیا این تابع با uninstall.php تفاوت دارد؟

بله، در غیرفعال‌سازی، داده‌ها حفظ می‌شوند. در uninstall (حذف افزونه)، داده‌ها پاک می‌شوند. هر دو مرحله منطق متفاوتی دارند و باید جداگانه مدیریت شوند.

نگاه فنی عمیق به register_deactivation_hook

در سطح معماری، register_deactivation_hook() یک رکورد در آرایه سراسری $wp_filter['deactivate_' . $file] ثبت می‌کند که در زمان غیرفعال‌سازی با do_action اجرا می‌شود. وردپرس از hook اختصاصی به نام deactivate_{plugin_basename} استفاده می‌کند.

نکته ظریف اول، مسئله priority است. اگر چند افزونه در حال غیرفعال‌سازی باشند، ترتیب اجرای hookها بر اساس priority تعیین می‌شود. اما در عمل، هر hook مربوط به یک افزونه مستقل است و ترتیب معمولاً مهم نیست.

نکته دوم، تعامل با Transient و Cache است. اگر در غیرفعال‌سازی، Transientها را پاک کنید، در فعال‌سازی بعدی، این Transientها باید دوباره ساخته شوند. توصیه می‌شود در غیرفعال‌سازی، حتماً wp_cache_flush را برای گروه کش افزونه فراخوانی کنید. مطلب رفتار wp_cache_flush توضیحات کامل را دارد.

مسئله سوم، مسئله uninstall کامل است. اگر بخواهید تمام داده‌های افزونه را در غیرفعال‌سازی حذف کنید، باید مراقب باشید. الگوی استاندارد: در غیرفعال‌سازی فقط داده‌های موقت، در uninstall همه داده‌ها. برای مطالعه بیشتر، مطلب تابع delete_option و حذف اسناد منقضی مفید هستند.

در نهایت، در پروژه‌های Enterprise توصیه می‌شود یک کلاس Installer اختصاصی بسازید که هم activate و هم deactivate را مدیریت کند و از یک منطق مشترک برای تشخیص داده موقت و دائمی استفاده کند. این الگو از تکرار منطق جلوگیری می‌کند و تست‌پذیری را بالا می‌برد. برای مطالعه بیشتر، مباحث تابع plugin_basename، تابع plugin_dir_path و تابع register_activation_hook مفید هستند. برای مطالعه بیشتر درباره خود وردپرس، WordPress در ویکی‌پدیا نقطه شروع خوبی است.

اگر در پروژه‌ای با مشکل باقی‌ماندن cron job یا داده موقت پس از غیرفعال‌سازی مواجه شده‌اید، برای ما جالب است بدانید کدام راهکار عملاً به حل مسئله کمک کرده است. تجربه خود را در دیدگاه‌ها بنویسید تا برای سایر توسعه‌دهندگان هم مفید باشد.